From 4d4bcf18c64901fcfceca956c73a88d5937dbabd Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 12 Jan 2015 15:23:45 +0100 Subject: [PATCH 01/40] add global status check --- src/exometer.erl | 40 ++++++++++++++++++++++++++++++++++++++-- src/exometer_admin.erl | 7 ++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/exometer.erl b/src/exometer.erl index f2312d2..e5b7f8f 100644 --- a/src/exometer.erl +++ b/src/exometer.erl @@ -60,6 +60,8 @@ register_application/1 ]). +-export([global_status/1]). + -export([create_entry/1]). % called only from exometer_admin.erl %% Convenience function for testing @@ -198,7 +200,13 @@ ensure(Name, Type, Opts) when is_list(Name), is_list(Opts) -> %% corresponding callback module will be called. For a disabled metric, %% `ok' will be returned without any other action being taken. %% @end -update(Name, Value) when is_list(Name) -> +update(Name, Value) -> + case exometer_global:status() of + enabled -> update_(Name, Value); + _ -> ok + end. + +update_(Name, Value) when is_list(Name) -> case ets:lookup(Table = exometer_util:table(), Name) of [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> @@ -427,7 +435,13 @@ sample(Name) when is_list(Name) -> %% the exometer entry, which can be recalled using {@link info/2}, and will %% indicate the time that has passed since the metric was last reset. %% @end -reset(Name) when is_list(Name) -> +reset(Name) -> + case exometer_global:status() of + enabled -> reset_(Name); + _ -> ok + end. + +reset_(Name) when is_list(Name) -> case ets:lookup(exometer_util:table(), Name) of [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> case E of @@ -807,6 +821,28 @@ aggr_acc([{D,V}|T], Acc) -> aggr_acc([], Acc) -> Acc. +global_status(St) when St==enabled; St==disabled -> + Prev = exometer_global:status(), + if St =:= Prev -> ok; + true -> + parse_trans_mod:transform_module( + exometer_global, fun(Forms,_) -> pt(Forms, St) end, []) + end, + Prev. + +pt(Forms, St) -> + parse_trans:plain_transform( + fun(F) -> + plain_pt(F, St) + end, Forms). + +plain_pt({function, L, status, 0, [_]}, St) -> + {function, L, status, 0, + [{clause, L, [], [], [{atom, L, St}]}]}; +plain_pt(_, _) -> + continue. + + %% Perform variable replacement in the ets select pattern. %% We want to project the entries as a set of {Name, Type, Status} tuples. %% This means we need to perform variable substitution in both guards and diff --git a/src/exometer_admin.erl b/src/exometer_admin.erl index cbe361e..603d61c 100644 --- a/src/exometer_admin.erl +++ b/src/exometer_admin.erl @@ -146,7 +146,12 @@ do_load_predef(Src, L) when is_list(L) -> (Other) -> lager:error("Predef(~p): ~p~n", [Src, {bad_pattern,Other}]) - end, Found) + end, Found); + ({aliases, Aliases}) -> + lists:foreach( + fun({Alias, Entry, DP}) -> + exometer_alias:new(Alias, Entry, DP) + end, Aliases) end, L). predef_delete_entry(Key, Src) -> From 708d99fb444762c3edd85367da0287d7256ceab0 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 12 Jan 2015 23:29:26 +0100 Subject: [PATCH 02/40] add exometer_global.erl --- src/exometer_global.erl | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/exometer_global.erl diff --git a/src/exometer_global.erl b/src/exometer_global.erl new file mode 100644 index 0000000..bcb56b3 --- /dev/null +++ b/src/exometer_global.erl @@ -0,0 +1,7 @@ +-module(exometer_global). + +-export([status/0]). + + +status() -> + enabled. From d83d58fb98aebcc706cf7c6d7994263e20a21ab6 Mon Sep 17 00:00:00 2001 From: Tino Breddin Date: Thu, 15 Jan 2015 10:06:29 +0100 Subject: [PATCH 03/40] update OTP target platforms for travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 075b529..2e9a497 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,12 @@ +sudo: false language: erlang script: "make ci" otp_release: + - 17.3 - 17.1 - 17.0 + - R16B03-1 + - R16B03 - R16B02 - R16B01 - R16B From 05c879667776a27f0abdd203814c2a0f3fe317f7 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Thu, 15 Jan 2015 11:37:03 +0100 Subject: [PATCH 04/40] Pick percentile items consistently in histograms The slot_slide version of histograms picks percentile items from the skew heap (if configured). In this case, it picks from a reversed list, since the heap keeps only the largest values. A slight difference in percent calculations could cause an off-by-one error when comparing the items picked by the exometer_slide and the exometer_slot_slide histograms. --- src/exometer_histogram.erl | 17 ++++++----------- src/exometer_util.erl | 3 ++- test/exometer_SUITE.erl | 13 +++++++++++++ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/exometer_histogram.erl b/src/exometer_histogram.erl index e4fdbd8..c0d7537 100644 --- a/src/exometer_histogram.erl +++ b/src/exometer_histogram.erl @@ -246,18 +246,13 @@ revsort(L) -> lists:sort(fun erlang:'>'/2, L). p(50, N) -> perc(0.5, N); -p(75, N) -> perc(0.25, N); -p(90, N) -> perc(0.1, N); -p(95, N) -> perc(0.05, N); -p(99, N) -> perc(0.01, N); -p(999,N) -> perc(0.001, N). - -perc(P, Len) when P > 1.0 -> - round((P / 10) * Len) + 1; -perc(P, Len) -> - round(P * Len) + 1. - +p(75, N) -> perc(0.75, N); +p(90, N) -> perc(0.9, N); +p(95, N) -> perc(0.95, N); +p(99, N) -> perc(0.99, N); +p(999,N) -> perc(0.999, N). +perc(P, N) -> N - exometer_util:perc(P, N) + 1. add_extra(Length, L, []) -> {Length, L}; diff --git a/src/exometer_util.erl b/src/exometer_util.erl index dda73fd..add0006 100644 --- a/src/exometer_util.erl +++ b/src/exometer_util.erl @@ -22,6 +22,7 @@ get_statistics/3, get_statistics2/4, pick_items/2, + perc/2, histogram/1, histogram/2, drop_duplicates/1, @@ -247,7 +248,7 @@ get_statistics2(L, Sorted, Total, Mean) -> Items = [{min,1}, {50, P50}, {median, P50}, {75, perc(0.75,L)}, {90, perc(0.9,L)}, {95, perc(0.95,L)}, {99, perc(0.99,L)}, {999, perc(0.999,L)}, {max,L}], - [{n,L}, {mean, Mean}, {total, Total} | pick_items(Sorted, 1, Items)]. + [{n,L}, {mean, Mean}, {total, Total} | pick_items(Sorted, Items)]. -spec pick_items([number()], [{atom() | integer(), integer()}]) -> [{atom(), number()}]. diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index 8223754..f65822d 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -27,6 +27,7 @@ test_update_or_create2/1, test_default_override/1, test_std_histogram/1, + test_slot_histogram/1, test_std_duration/1, test_folsom_histogram/1, test_aggregate/1, @@ -78,6 +79,7 @@ groups() -> {test_histogram, [shuffle], [ test_std_histogram, + test_slot_histogram, test_std_duration, test_folsom_histogram, test_aggregate, @@ -240,6 +242,17 @@ test_std_histogram(_Config) -> {50,2},{75,3},{90,4},{95,5},{99,8},{999,9}] = scale_mean(DPs), ok. +test_slot_histogram(_Config) -> + C = [?MODULE, hist, ?LINE], + ok = exometer:new(C, histogram, [{histogram_module, exometer_slot_slide}, + {keep_high, 100}, + {truncate, false}]), + [ok = update_(C,V) || V <- vals()], + {_, {ok,DPs}} = timer:tc(exometer, get_value, [C]), + [{n,_},{mean,2126866},{min,1},{max,9},{median,2}, + {50,2},{75,3},{90,4},{95,5},{99,8},{999,9}] = scale_mean(DPs), + ok. + test_std_duration(_Config) -> C = [?MODULE, dur, ?LINE], ok = exometer:new(C, duration, []), From 291797af8230717baad226ddb6a573d213be252a Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Thu, 29 Jan 2015 21:37:09 +0100 Subject: [PATCH 05/40] Adjust reporter timers against drift --- src/exometer_report.erl | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/exometer_report.erl b/src/exometer_report.erl index d154bd2..cb8f393 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -1014,11 +1014,12 @@ handle_info({report_batch, Reporter, Name}, #st{} = St) -> report_batch(Reporter, Name), {noreply, St}; handle_info({report, #key{reporter = Reporter} = Key, Interval}, #st{} = St) -> + T0 = os:timestamp(), case ets:member(?EXOMETER_SUBS, Key) andalso get_reporter_status(Reporter) == enabled of true -> case do_report(Key, Interval) of - true -> restart_subscr_timer(Key, Interval); + true -> restart_subscr_timer(Key, Interval, T0); false -> ok end, {noreply, St}; @@ -1119,6 +1120,7 @@ do_report(#key{metric = Metric, end. report_batch(Reporter, Name) when is_atom(Name) -> + T0 = os:timestamp(), case ets:lookup(?EXOMETER_REPORTERS, Reporter) of [#reporter{status = disabled}] -> false; @@ -1132,7 +1134,7 @@ report_batch(Reporter, Name) when is_atom(Name) -> fun(#subscriber{key = Key}) -> do_report(Key, Name) end, Entries), - restart_batch_timer(Name, R); + restart_batch_timer(Name, R, T0); [] -> false end. @@ -1149,19 +1151,19 @@ cancel_subscr_timers(Reporter) -> _ = '_'}, _ = '_'}, [], ['$_']}])). -restart_subscr_timer(Key, Interval) when is_integer(Interval) -> - TRef = erlang:send_after(Interval, self(), +restart_subscr_timer(Key, Interval, T0) when is_integer(Interval) -> + TRef = erlang:send_after(adjust_interval(Interval, T0), self(), {report, Key, Interval}), ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]); -restart_subscr_timer(_, _) -> +restart_subscr_timer(_, _, _) -> true. restart_batch_timer(Name, #reporter{name = Reporter, - intervals = Ints}) when is_list(Ints) -> + intervals = Ints}, T0) when is_list(Ints) -> case lists:keyfind(Name, #interval.name, Ints) of #interval{time = Time} = I -> - TRef = erlang:send_after(Time, self(), + TRef = erlang:send_after(adjust_interval(Time, T0), self(), batch_timer_msg(Reporter, Name)), ets:update_element(?EXOMETER_REPORTERS, Reporter, [{#reporter.intervals, @@ -1171,6 +1173,10 @@ restart_batch_timer(Name, #reporter{name = Reporter, false end. +adjust_interval(Time, T0) -> + T1 = os:timestamp(), + erlang:max(0, Time - (timer:now_diff(T1, T0) div 1000)). + cancel_timer(undefined) -> false; cancel_timer(TRef) -> From e66a3f73ae8d8e21341d0d27688c043acc8fddc9 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 30 Jan 2015 21:08:32 +0100 Subject: [PATCH 06/40] include timestamp in timeout msgs --- src/exometer_report.erl | 71 ++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/src/exometer_report.erl b/src/exometer_report.erl index cb8f393..64a9091 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -697,11 +697,11 @@ start_interval_timer(#interval{name = Name, delay = Delay, end. do_start_interval_timer(#interval{name = Name, time = Time} = I, R) -> - TRef = erlang:send_after(Time, self(), batch_timer_msg(R, Name)), + TRef = erlang:send_after(Time, self(), batch_timer_msg(R, Name, Time)), I#interval{t_ref = TRef}. -batch_timer_msg(R, Name) -> - {report_batch, R, Name}. +batch_timer_msg(R, Name, Time) -> + {report_batch, R, Name, Time, os:timestamp()}. get_report_env() -> Opts0 = exometer_util:get_env(report, []), @@ -1011,23 +1011,20 @@ handle_info({start_interval, Reporter, Name}, #st{} = St) -> handle_info({report_batch, Reporter, Name}, #st{} = St) -> %% Find all entries where reporter is Reporter and interval is Name, %% and report them. - report_batch(Reporter, Name), + report_batch(Reporter, Name, os:timestamp()), {noreply, St}; -handle_info({report, #key{reporter = Reporter} = Key, Interval}, #st{} = St) -> - T0 = os:timestamp(), - case ets:member(?EXOMETER_SUBS, Key) andalso - get_reporter_status(Reporter) == enabled of - true -> - case do_report(Key, Interval) of - true -> restart_subscr_timer(Key, Interval, T0); - false -> ok - end, - {noreply, St}; - false -> - %% Possibly an unsubscribe removed the subscriber - ?error("No such subscriber (Key=~p)~n", [Key]), - {noreply, St} - end; +handle_info({report_batch, Reporter, Name, Int, TS}, #st{} = St) -> + %% Find all entries where reporter is Reporter and interval is Name, + %% and report them. + TS1 = calc_fire_time(TS, Int), + report_batch(Reporter, Name, TS1), + {noreply, St}; +handle_info({report, #key{} = Key, Interval}, #st{} = St) -> + %% BW Compat. Old-style timeout msg, which doesn't include timestamp + {noreply, handle_report(Key, Interval, os:timestamp(), St)}; +handle_info({report, #key{} = Key, Interval, TS}, #st{} = St) -> + TS1 = calc_fire_time(TS, Interval), + {noreply, handle_report(Key, Interval, TS1, St)}; handle_info({'DOWN', Ref, process, _Pid, Reason}, #st{} = S) -> case reporter_by_mref(Ref) of @@ -1092,6 +1089,19 @@ resubscribe(#subscriber{key = #key{reporter = RName, TRef = erlang:send_after(Interval, self(), {report, Key, Interval}), ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]). +handle_report(#key{reporter = Reporter} = Key, Interval, TS, #st{} = St) -> + _ = case ets:member(?EXOMETER_SUBS, Key) andalso + get_reporter_status(Reporter) == enabled of + true -> + case do_report(Key, Interval) of + true -> restart_subscr_timer(Key, Interval, TS); + false -> ok + end; + false -> + %% Possibly an unsubscribe removed the subscriber + ?error("No such subscriber (Key=~p)~n", [Key]) + end, + St. do_report(#key{metric = Metric, datapoint = DataPoint, @@ -1119,8 +1129,7 @@ do_report(#key{metric = Metric, false end. -report_batch(Reporter, Name) when is_atom(Name) -> - T0 = os:timestamp(), +report_batch(Reporter, Name, T0) when is_atom(Name) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of [#reporter{status = disabled}] -> false; @@ -1153,7 +1162,7 @@ cancel_subscr_timers(Reporter) -> restart_subscr_timer(Key, Interval, T0) when is_integer(Interval) -> TRef = erlang:send_after(adjust_interval(Interval, T0), self(), - {report, Key, Interval}), + {report, Key, Interval, os:timestamp()}), ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]); restart_subscr_timer(_, _, _) -> @@ -1163,8 +1172,9 @@ restart_batch_timer(Name, #reporter{name = Reporter, intervals = Ints}, T0) when is_list(Ints) -> case lists:keyfind(Name, #interval.name, Ints) of #interval{time = Time} = I -> - TRef = erlang:send_after(adjust_interval(Time, T0), self(), - batch_timer_msg(Reporter, Name)), + T1 = adjust_interval(Time, T0), + TRef = erlang:send_after(T1, self(), + batch_timer_msg(Reporter, Name, T1)), ets:update_element(?EXOMETER_REPORTERS, Reporter, [{#reporter.intervals, lists:keyreplace(Name, #interval.name, Ints, @@ -1175,7 +1185,16 @@ restart_batch_timer(Name, #reporter{name = Reporter, adjust_interval(Time, T0) -> T1 = os:timestamp(), - erlang:max(0, Time - (timer:now_diff(T1, T0) div 1000)). + erlang:max(0, Time - tdiff(T1, T0)). + +tdiff(T1, T0) -> + timer:now_diff(T1, T0) div 1000. + +%% Calculate time when timer should have fired, based on timestamp logged +%% at send_after/3 and the intended interval (in ms). +calc_fire_time({M,S,U}, Int) -> + {M, S, U + (Int*1000)}. + cancel_timer(undefined) -> false; @@ -1376,7 +1395,7 @@ subscribe_(Reporter, Metric, DataPoint, Interval, RetryFailedMetrics, t_ref = maybe_send_after(Status, Key, Interval)}). maybe_send_after(enabled, Key, Interval) when is_integer(Interval) -> - erlang:send_after(Interval, self(), {report, Key, Interval}); + erlang:send_after(Interval, self(), {report, Key, Interval, os:timestamp()}); maybe_send_after(_, _, _) -> undefined. From b31a7918f6c43de21c3df137fc4515a7ed3e4ce6 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 2 Feb 2015 15:41:01 +0100 Subject: [PATCH 07/40] pass T0 in timeout msg --- src/exometer_report.erl | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/exometer_report.erl b/src/exometer_report.erl index 64a9091..eb9878d 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -701,7 +701,16 @@ do_start_interval_timer(#interval{name = Name, time = Time} = I, R) -> I#interval{t_ref = TRef}. batch_timer_msg(R, Name, Time) -> - {report_batch, R, Name, Time, os:timestamp()}. + batch_timer_msg(R, Name, Time, os:timestamp()). + +batch_timer_msg(R, Name, Time, TS) -> + {report_batch, R, Name, Time, TS}. + +subscr_timer_msg(Key, Interval) -> + subscr_timer_msg(Key, Interval, os:timestamp()). + +subscr_timer_msg(Key, Interval, TS) -> + {report, Key, Interval, TS}. get_report_env() -> Opts0 = exometer_util:get_env(report, []), @@ -1086,7 +1095,8 @@ resubscribe(#subscriber{key = #key{reporter = RName, interval = Interval}) -> try_send(RName, {exometer_subscribe, Metric, DataPoint, Interval, Extra}), cancel_timer(OldTRef), - TRef = erlang:send_after(Interval, self(), {report, Key, Interval}), + TRef = erlang:send_after(Interval, self(), + subscr_timer_msg(Key, Interval)), ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]). handle_report(#key{reporter = Reporter} = Key, Interval, TS, #st{} = St) -> @@ -1162,7 +1172,7 @@ cancel_subscr_timers(Reporter) -> restart_subscr_timer(Key, Interval, T0) when is_integer(Interval) -> TRef = erlang:send_after(adjust_interval(Interval, T0), self(), - {report, Key, Interval, os:timestamp()}), + subscr_timer_msg(Key, Interval, T0)), ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]); restart_subscr_timer(_, _, _) -> @@ -1172,9 +1182,9 @@ restart_batch_timer(Name, #reporter{name = Reporter, intervals = Ints}, T0) when is_list(Ints) -> case lists:keyfind(Name, #interval.name, Ints) of #interval{time = Time} = I -> - T1 = adjust_interval(Time, T0), - TRef = erlang:send_after(T1, self(), - batch_timer_msg(Reporter, Name, T1)), + TRef = erlang:send_after( + adjust_interval(Time, T0), self(), + batch_timer_msg(Reporter, Name, Time, T0)), ets:update_element(?EXOMETER_REPORTERS, Reporter, [{#reporter.intervals, lists:keyreplace(Name, #interval.name, Ints, @@ -1395,7 +1405,8 @@ subscribe_(Reporter, Metric, DataPoint, Interval, RetryFailedMetrics, t_ref = maybe_send_after(Status, Key, Interval)}). maybe_send_after(enabled, Key, Interval) when is_integer(Interval) -> - erlang:send_after(Interval, self(), {report, Key, Interval, os:timestamp()}); + erlang:send_after( + Interval, self(), subscr_timer_msg(Key, Interval)); maybe_send_after(_, _, _) -> undefined. From 9f9e78b144ba0f79f0595819793b6fae711e4afc Mon Sep 17 00:00:00 2001 From: Jan Stypka Date: Thu, 5 Feb 2015 11:45:42 +0100 Subject: [PATCH 08/40] switched the order of arguments in the callback definition --- src/exometer_report.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exometer_report.erl b/src/exometer_report.erl index eb9878d..ce8651a 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -216,7 +216,7 @@ -callback exometer_init(options()) -> callback_result(). -callback exometer_report(metric(), datapoint(), - value(), extra(), mod_state()) -> + extra(), value(), mod_state()) -> callback_result(). -callback exometer_subscribe(metric(), datapoint(), From 4f7da4c38acd08f5a133850196765ee275fa65dc Mon Sep 17 00:00:00 2001 From: Alexei Sholik Date: Thu, 12 Feb 2015 16:53:16 +0200 Subject: [PATCH 09/40] Avoid crashing when calling resubscribe() with a non-integer interval --- src/exometer_report.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/exometer_report.erl b/src/exometer_report.erl index ce8651a..619404b 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -1092,12 +1092,14 @@ resubscribe(#subscriber{key = #key{reporter = RName, datapoint = DataPoint, extra = Extra} = Key, t_ref = OldTRef, - interval = Interval}) -> + interval = Interval}) when is_integer(Interval) -> try_send(RName, {exometer_subscribe, Metric, DataPoint, Interval, Extra}), cancel_timer(OldTRef), TRef = erlang:send_after(Interval, self(), subscr_timer_msg(Key, Interval)), - ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]). + ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]); + +resubscribe(_) -> undefined. handle_report(#key{reporter = Reporter} = Key, Interval, TS, #st{} = St) -> _ = case ets:member(?EXOMETER_SUBS, Key) andalso From 1527a822353ef85c9001eeac565603c4384832bb Mon Sep 17 00:00:00 2001 From: David Reid Date: Thu, 19 Feb 2015 15:06:55 -0800 Subject: [PATCH 10/40] Bump folsom dependency to 0.8.2 --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 71194b4..8332680 100644 --- a/rebar.config +++ b/rebar.config @@ -12,7 +12,7 @@ {lager, ".*", {git, "git://github.com/basho/lager.git", {tag,"2.0.3"}}}, {parse_trans, ".*", {git, "git://github.com/uwiger/parse_trans.git", {tag,"2.9"}}}, {meck, ".*", {git, "git://github.com/eproxus/meck.git", {tag,"0.8.2"}}}, - {folsom, ".*", {git, "git://github.com/boundary/folsom", {tag, "0.8.1"}}}, + {folsom, ".*", {git, "git://github.com/boundary/folsom", {tag, "0.8.2"}}}, {setup, ".*", {git, "git://github.com/uwiger/setup.git", {tag,"1.4"}}} ]}. From 1e94744ea9956a92bc11b6499b3c17703b751646 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 2 Mar 2015 16:45:07 +0100 Subject: [PATCH 11/40] Add checks for alias maps + test suite --- src/exometer_alias.erl | 100 ++++++++++++++++++-- test/exometer_alias_SUITE.erl | 168 ++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 9 deletions(-) create mode 100644 test/exometer_alias_SUITE.erl diff --git a/src/exometer_alias.erl b/src/exometer_alias.erl index c7178dd..7ddd3e7 100644 --- a/src/exometer_alias.erl +++ b/src/exometer_alias.erl @@ -75,11 +75,13 @@ %% type, and returns `{error, exists}' if the alias has already been %% registered. %% @end -new(Alias, Entry, DP) when is_list(Entry), is_atom(DP), is_atom(Alias); - is_list(Entry), is_atom(DP), is_binary(Alias); - is_list(Entry), is_integer(DP), is_atom(Alias); - is_list(Entry), is_integer(DP), is_binary(Alias) -> - gen_server:call(?MODULE, {new, Alias, Entry, DP}). +new(Alias, Entry, DP) -> + case valid_arg(Alias, Entry, DP) of + true -> + gen_server:call(?MODULE, {new, Alias, Entry, DP}); + false -> + {error, invalid} + end. -spec load(fun(() -> stat_map())) -> ok. %% @doc Load a list of mappings between entry+datapoint pairs and aliases. @@ -425,19 +427,99 @@ to_key(A) when is_binary(A) -> do_load(F) -> Map = F(), + check_map(Map), lists:foreach( fun({Entry, DPs}) when is_list(Entry), is_list(DPs) -> lists:foreach( fun({DP, Alias}) when is_atom(DP), is_atom(Alias); is_atom(DP), is_binary(Alias) -> Key = to_key(Alias), - ets:insert(?TAB, #alias{key = Key, - alias = Alias, - entry = Entry, - dp = DP}) + true = ets:insert_new(?TAB, #alias{key = Key, + alias = Alias, + entry = Entry, + dp = DP}) end, DPs) end, Map). +check_map(Map) -> + case lists:foldl( + fun(F, {M1,Es}) -> + F(M1, Es) + end, {Map, []}, [fun check_args/2, + fun check_duplicates/2, + fun check_existing/2]) of + {Map1, []} -> + Map1; + {_, Errors} -> + error({map_error, Errors}) + end. + +check_args(Map, Es) -> + Check = deep_fold( + fun({Alias, Entry, DP}, D) -> + case valid_arg(Alias, Entry, DP) of + true -> D; + false -> + orddict:append(Alias, {Entry, DP}, D) + end; + (Other, D) -> + orddict:append(unrecognized, Other, D) + end, orddict:new(), Map), + maybe_add_errors(Check, invalid, Map, Es). + +check_duplicates(Map, Es) -> + Check = deep_fold( + fun({Alias, Entry, DP}, D) -> + dict:append(Alias, {Entry,DP}, D); + (_, D) -> + D + end, dict:new(), Map), + %% We have duplicates if the value of any dict item is a list of length > 1. + Dups = dict:fold(fun(K, [_,_|_] = V, Acc) -> [{K, V}|Acc]; + (_, _, Acc) -> Acc + end, [], Check), + maybe_add_errors(Dups, duplicate_aliases, Map, Es). + +check_existing(Map, Es) -> + Check = deep_fold( + fun({Alias, Entry, DP}, Acc) -> + %% Accept identical entries + case resolve(Alias) of + {Entry, DP} -> Acc; + error -> Acc; + {OtherEntry, OtherDP} -> + orddict:append(Alias, {OtherEntry, OtherDP}, Acc) + end; + (_, D) -> + D + end, orddict:new(), Map), + maybe_add_errors(Check, existing_aliases, Map, Es). + +valid_arg(Alias, Entry, DP) + when is_list(Entry), is_atom(DP), is_atom(Alias); + is_list(Entry), is_atom(DP), is_binary(Alias); + is_list(Entry), is_integer(DP), is_atom(Alias); + is_list(Entry), is_integer(DP), is_binary(Alias) -> true; +valid_arg(_, _, _) -> + false. + + +maybe_add_errors([], _, Map, Es) -> {Map, Es}; +maybe_add_errors([_|_] = NewErrors, Kind, Map, Es) -> + {Map, [{Kind, NewErrors}|Es]}. + +deep_fold(F, Acc, Map) -> + lists:foldl( + fun({Entry, DPs}, Acc1) -> + lists:foldl( + fun({DP, Alias}, Acc2) -> + F({Alias, Entry, DP}, Acc2) + end, Acc1, DPs); + (Other, Acc1) -> + %% Bad input, but let's pass it on to the check function + F(Other, Acc1) + end, Acc, Map). + do_unload(F) -> Map = F(), lists:foreach( diff --git a/test/exometer_alias_SUITE.erl b/test/exometer_alias_SUITE.erl new file mode 100644 index 0000000..8bf0f92 --- /dev/null +++ b/test/exometer_alias_SUITE.erl @@ -0,0 +1,168 @@ +-module(exometer_alias_SUITE). + +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% common_test exports +-export( + [ + all/0, groups/0, suite/0, + init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2 + ]). + +%% test case exports +-export( + [ + test_register_alias/1, + test_load_map/1, + test_unload_map/1, + test_map_duplicates/1, + test_map_badargs/1, + test_map_existing/1, + test_map_multiple_errors/1 + ]). + +-include_lib("common_test/include/ct.hrl"). + +%%%=================================================================== +%%% common_test API +%%%=================================================================== + +all() -> + [ + {group, test_alias}, + {group, test_map} + ]. + +groups() -> + [ + {test_alias, [shuffle], + [ + test_register_alias + ]}, + {test_map, [shuffle], + [ + test_load_map, + test_unload_map, + test_map_duplicates, + test_map_badargs, + test_map_existing, + test_map_multiple_errors + ]} + ]. + +suite() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(Case, Config) -> + exometer:start(), + Config. + +end_per_testcase(_Case, Config) -> + exometer:stop(), + ok. + +%%%=================================================================== +%%% Test Cases +%%%=================================================================== +test_register_alias(_Config) -> + M = [?MODULE, ?LINE], + ok = exometer_alias:new(t1, M, value), + {M, value} = exometer_alias:resolve(t1), + ok = exometer_alias:delete(t1), + error = exometer_alias:resolve(t1), + ok. + +test_load_map(_Config) -> + ok = exometer_alias:load(fun map1/0), + {[map1,1], a} = exometer_alias:resolve(m11a), + {[map1,1], b} = exometer_alias:resolve(m11b), + {[map1,2], a} = exometer_alias:resolve(m12a), + {[map1,2], b} = exometer_alias:resolve(m12b), + ok. + +test_unload_map(Config) -> + ok = test_load_map(Config), + ok = exometer_alias:unload(fun map1/0), + error = exometer_alias:resolve(m11a), + error = exometer_alias:resolve(m11b), + error = exometer_alias:resolve(m12a), + error = exometer_alias:resolve(m12b), + ok. + +test_map_duplicates(_Config) -> + {error, {map_error, [{duplicate_aliases, + [{m21a, [{[map2,1], a}, + {[map2,1], b}]}] + }]}} = + exometer_alias:load(fun map2/0), + ok. + +test_map_badargs(_Config) -> + {error, {map_error, [{invalid, + [{m31a,[{{map3,1},a}]}] + }]}} = + exometer_alias:load(fun map3/0), + ok. + +test_map_existing(_Config) -> + exometer_alias:new(m11a, [some,metric], a), + {error, {map_error, [{existing_aliases, + [{m11a, [{[some,metric], a}]}]} + ]}} = + exometer_alias:load(fun map1/0), + ok. + +test_map_multiple_errors(_Config) -> + exometer_alias:new(m11a, [some,other,metric], a), + Map = fun() -> + map1() ++ map2() ++ map3() + end, + {error, {map_error, Errors}} = exometer_alias:load(Map), + [ + {duplicate_aliases, [ + {m21a,[{[map2,1],a},{[map2,1], b}]} + ]}, + {existing_aliases, [ + {m11a, [{[some,other,metric], a}]} + ]}, + {invalid, [ + {m31a, [{{map3,1},a}]} + ]} + ] = lists:sort(Errors), + ok. + +map1() -> + [ + {[map1, 1], [{a, m11a}, + {b, m11b}]}, + {[map1, 2], [{a, m12a}, + {b, m12b}]} + ]. + +%% contains error: duplicate aliases +map2() -> + [ + {[map2, 1], [{a, m21a}, + {b, m21a}]}, + {[map2, 2], [{a, m22a}, + {b, m22b}]} + ]. + +map3() -> + [ + {{map3, 1}, [{a, m31a}]} + ]. From 50e31e0c700a47235b503bffe6a75e5f6d89d98a Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Wed, 11 Mar 2015 13:18:21 +0100 Subject: [PATCH 12/40] verify number type for histogram updates --- src/exometer_histogram.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/exometer_histogram.erl b/src/exometer_histogram.erl index c0d7537..ab7efe8 100644 --- a/src/exometer_histogram.erl +++ b/src/exometer_histogram.erl @@ -295,7 +295,12 @@ probe_setopts(_Entry, _Opts, _St) -> probe_update(Value, ?OLDSTATE = St) -> probe_update(Value, convert(St)); probe_update(Value, St) -> - {ok, update_int(exometer_util:timestamp(), Value, St)}. + if is_number(Value) -> + {ok, update_int(exometer_util:timestamp(), Value, St)}; + true -> + %% ignore + {ok, St} + end. update_int(Timestamp, Value, #st{slide = Slide, histogram_module = Module, From 2759edc804211b5245867b32c9a20c8fe1d93441 Mon Sep 17 00:00:00 2001 From: Grzegorz Junka Date: Mon, 16 Mar 2015 10:09:33 +0000 Subject: [PATCH 13/40] No functional chagnes - only reformatting to keep it compatible with the standard Erlang mode in Emacs --- src/exo_montest.erl | 24 +- src/exometer.erl | 432 ++++++++-------- src/exometer_admin.erl | 320 ++++++------ src/exometer_alias.erl | 326 ++++++------ src/exometer_core_sup.erl | 12 +- src/exometer_cpu.erl | 2 +- src/exometer_duration.erl | 46 +- src/exometer_folsom.erl | 52 +- src/exometer_folsom_monitor.erl | 100 ++-- src/exometer_function.erl | 36 +- src/exometer_histogram.erl | 182 +++---- src/exometer_igor.erl | 4 +- src/exometer_info.erl | 16 +- src/exometer_probe.erl | 48 +- src/exometer_proc.erl | 6 +- src/exometer_report.erl | 888 ++++++++++++++++---------------- src/exometer_shallowtree.erl | 42 +- src/exometer_slide.erl | 2 +- src/exometer_slot_slide.erl | 28 +- src/exometer_spiral.erl | 36 +- src/exometer_uniform.erl | 14 +- src/exometer_util.erl | 50 +- 22 files changed, 1333 insertions(+), 1333 deletions(-) diff --git a/src/exo_montest.erl b/src/exo_montest.erl index b70a466..0641c26 100644 --- a/src/exo_montest.erl +++ b/src/exo_montest.erl @@ -18,29 +18,29 @@ -export([copy_folsom/3]). -export([behaviour/0, - delete/3, - get_datapoints/3, - get_value/4, - new/3, - reset/3, - sample/3, - setopts/3, - update/4]). + delete/3, + get_datapoints/3, + get_value/4, + new/3, + reset/3, + sample/3, + setopts/3, + update/4]). behaviour() -> entry. copy_folsom(Name, Type, Opts) when is_tuple(Name) -> {tuple_to_list(Name), ad_hoc, [{folsom_name, Name}, - {module, ?MODULE}, - {type, Type} - | options(Type, Opts)]}; + {module, ?MODULE}, + {type, Type} + | options(Type, Opts)]}; copy_folsom(_, _, _) -> false. new(N, _, Opts) -> {ok, {proplists:get_value(type, Opts, unknown), - proplists:get_value(folsom_name, Opts, N)}}. + proplists:get_value(folsom_name, Opts, N)}}. update(_, Value, counter, {_, Name}) -> folsom_metrics:notify_existing_metric(Name, {inc,Value}, counter); diff --git a/src/exometer.erl b/src/exometer.erl index 114d857..140010c 100644 --- a/src/exometer.erl +++ b/src/exometer.erl @@ -66,7 +66,7 @@ -export([start/0, stop/0]). -export_type([name/0, type/0, options/0, status/0, behaviour/0, - entry/0, datapoint/0]). + entry/0, datapoint/0]). -compile(inline). @@ -203,38 +203,38 @@ ensure(Name, Type, Opts) when is_list(Name), is_list(Opts) -> %% @end update(Name, Value) when is_list(Name) -> case ets:lookup(Table = exometer_util:table(), Name) of - [#exometer_entry{status = Status} = E] - when ?IS_ENABLED(Status) -> - case E of - #exometer_entry{module = ?MODULE, type = counter} -> + [#exometer_entry{status = Status} = E] + when ?IS_ENABLED(Status) -> + case E of + #exometer_entry{module = ?MODULE, type = counter} -> ets:update_counter( Table, Name, {#exometer_entry.value, Value}); - #exometer_entry{module = ?MODULE, - type = fast_counter, ref = {M, F}} -> + #exometer_entry{module = ?MODULE, + type = fast_counter, ref = {M, F}} -> fast_incr(Value, M, F); - #exometer_entry{module = ?MODULE, type = gauge} -> - ets:update_element( - ?EXOMETER_ENTRIES, - Name, [{#exometer_entry.value, Value}]); - #exometer_entry{behaviour = probe, - type = T, ref = Pid} -> + #exometer_entry{module = ?MODULE, type = gauge} -> + ets:update_element( + ?EXOMETER_ENTRIES, + Name, [{#exometer_entry.value, Value}]); + #exometer_entry{behaviour = probe, + type = T, ref = Pid} -> exometer_probe:update(Name, Value, T, Pid); - #exometer_entry{module = M, behaviour = entry, - type = Type, ref = Ref} -> - M:update(Name, Value, Type, Ref) - end, - update_ok(Status, E, Value); + #exometer_entry{module = M, behaviour = entry, + type = Type, ref = Ref} -> + M:update(Name, Value, Type, Ref) + end, + update_ok(Status, E, Value); [] -> {error, not_found}; - _ -> - ok + _ -> + ok end. update_ok(St, #exometer_entry{name = Name}, Value) when St band 2#10 =:= 2#10 -> try exometer_event ! {updated, Name, Value} catch - _:_ -> ok + _:_ -> ok end; update_ok(_, _, _) -> ok. @@ -251,24 +251,24 @@ update_ok(_, _, _) -> %% @end update_or_create(Name, Value) -> case update(Name, Value) of - {error, not_found} -> - case exometer_admin:auto_create_entry(Name) of - ok -> - update(Name, Value); - Error -> - Error - end; - ok -> - ok + {error, not_found} -> + case exometer_admin:auto_create_entry(Name) of + ok -> + update(Name, Value); + Error -> + Error + end; + ok -> + ok end. update_or_create(Name, Value, Type, Opts) -> case update(Name, Value) of - {error, not_found} -> - ensure(Name, Type, Opts), - update(Name, Value); - ok -> - ok + {error, not_found} -> + ensure(Name, Type, Opts), + update(Name, Value); + ok -> + ok end. fast_incr(N, M, F) when N > 0 -> @@ -292,7 +292,7 @@ get_value(Name) when is_list(Name) -> get_value(Name, default). -spec get_value(name(), datapoint() | [datapoint()]) -> - {ok, value()} | {error, not_found}. + {ok, value()} | {error, not_found}. get_value(Name, DataPoint) when is_list(Name), is_integer(DataPoint) -> get_value(Name, [DataPoint]); @@ -317,21 +317,21 @@ get_value_(#exometer_entry{ status = Status }, _DataPoints) %% If the value is cached, see if we can find it. %% In the default case, call again with resolved data points. get_value_(#exometer_entry{cache = Cache } = E, - DataPoints) when Cache =/= 0 -> + DataPoints) when Cache =/= 0 -> get_cached_value_(E, DataPoints); get_value_(#exometer_entry{ module = ?MODULE, - type = Type} = E, default) + type = Type} = E, default) when Type == counter; Type == fast_counter; Type == gauge -> get_value_(E, exometer_util:get_datapoints(E)); get_value_(#exometer_entry{ module = ?MODULE, - type = counter} = E, DataPoints0) -> + type = counter} = E, DataPoints0) -> DataPoints = datapoints(DataPoints0, E), [get_ctr_datapoint(E, D) || D <- DataPoints]; get_value_(#exometer_entry{ module = ?MODULE, - type = gauge, name = Name}, DataPoints0) -> + type = gauge, name = Name}, DataPoints0) -> [E] = ets:lookup(?EXOMETER_ENTRIES, Name), DataPoints = datapoints(DataPoints0, E), [get_gauge_datapoint(E, D) || D <- DataPoints]; @@ -342,16 +342,16 @@ get_value_(#exometer_entry{module = ?MODULE, [get_fctr_datapoint(E, D) || D <- DataPoints ]; get_value_(#exometer_entry{behaviour = entry, - module = Mod, - name = Name, - type = Type, - ref = Ref} = E, DataPoints0) -> + module = Mod, + name = Name, + type = Type, + ref = Ref} = E, DataPoints0) -> Mod:get_value(Name, Type, Ref, datapoints(DataPoints0, E)); get_value_(#exometer_entry{behaviour = probe, - name = Name, - type = Type, - ref = Ref} = E, DataPoints0) -> + name = Name, + type = Type, + ref = Ref} = E, DataPoints0) -> exometer_probe:get_value(Name, Type, Ref, datapoints(DataPoints0, E)). @@ -360,21 +360,21 @@ get_cached_value_(E, default) -> get_cached_value_(E, exometer_util:get_datapoints(E)); get_cached_value_(#exometer_entry{name = Name, - cache = CacheTTL } = E, - DataPoints) -> + cache = CacheTTL } = E, + DataPoints) -> %% Dig through all the data points and check for cache hit. %% Store all cached KV pairs, and all keys that must be %% read and cached. { Cached, Uncached } = - lists:foldr(fun(DataPoint, {Cached1, Uncached1}) -> - case exometer_cache:read(Name, DataPoint) of - not_found -> - { Cached1, [DataPoint | Uncached1] }; - {_, Value } -> - { [{ DataPoint, Value } | Cached1], Uncached1 } - end - end, {[],[]}, DataPoints), + lists:foldr(fun(DataPoint, {Cached1, Uncached1}) -> + case exometer_cache:read(Name, DataPoint) of + not_found -> + { Cached1, [DataPoint | Uncached1] }; + {_, Value } -> + { [{ DataPoint, Value } | Cached1], Uncached1 } + end + end, {[],[]}, DataPoints), %% Go through all cache misses and retreive their actual values. Result = get_value_(E#exometer_entry { cache = 0 }, Uncached), @@ -402,20 +402,20 @@ delete(Name) when is_list(Name) -> %% @end sample(Name) when is_list(Name) -> case ets:lookup(exometer_util:table(), Name) of - [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> - case E of - #exometer_entry{module = ?MODULE, type = counter} -> ok; - #exometer_entry{module = ?MODULE, type = fast_counter} -> ok; - #exometer_entry{behaviour = probe, - type = Type, - ref = Ref} -> - exometer_probe:sample(Name, Type, Ref), - ok; - #exometer_entry{behaviour = entry, - module = M, type = Type, ref = Ref} -> - M:sample(Name, Type, Ref) - end; - [_] -> disabled; + [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> + case E of + #exometer_entry{module = ?MODULE, type = counter} -> ok; + #exometer_entry{module = ?MODULE, type = fast_counter} -> ok; + #exometer_entry{behaviour = probe, + type = Type, + ref = Ref} -> + exometer_probe:sample(Name, Type, Ref), + ok; + #exometer_entry{behaviour = entry, + module = M, type = Type, ref = Ref} -> + M:sample(Name, Type, Ref) + end; + [_] -> disabled; [] -> {error, not_found} end. @@ -432,42 +432,42 @@ sample(Name) when is_list(Name) -> %% @end reset(Name) when is_list(Name) -> case ets:lookup(exometer_util:table(), Name) of - [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> - case E of - #exometer_entry{module = ?MODULE, type = counter} -> - TS = exometer_util:timestamp(), - [ets:update_element(T, Name, [{#exometer_entry.value, 0}, - {#exometer_entry.timestamp, TS}]) - || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], - ok; - #exometer_entry{module = ?MODULE, type = fast_counter, - ref = {M, F}} -> - TS = exometer_util:timestamp(), - set_call_count(M, F, true), - [ets:update_element(T, Name, [{#exometer_entry.timestamp, TS}]) - || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], - ok; - #exometer_entry{module = ?MODULE, type = gauge} -> - TS = exometer_util:timestamp(), - [ets:update_element(T, Name, [{#exometer_entry.value, 0}, - {#exometer_entry.timestamp, TS}]) - || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], - ok; - #exometer_entry{behaviour = probe, type = Type, ref = Ref} -> - [exometer_cache:delete(Name, DataPoint) || - DataPoint <- exometer_util:get_datapoints(E)], - exometer_probe:reset(Name, Type, Ref), - ok; - #exometer_entry{behaviour = entry, - module = M, type = Type, ref = Ref} -> - [exometer_cache:delete(Name, DataPoint) || - DataPoint <- exometer_util:get_datapoints(E)], - M:reset(Name, Type, Ref) - end; - [] -> + [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> + case E of + #exometer_entry{module = ?MODULE, type = counter} -> + TS = exometer_util:timestamp(), + [ets:update_element(T, Name, [{#exometer_entry.value, 0}, + {#exometer_entry.timestamp, TS}]) + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + #exometer_entry{module = ?MODULE, type = fast_counter, + ref = {M, F}} -> + TS = exometer_util:timestamp(), + set_call_count(M, F, true), + [ets:update_element(T, Name, [{#exometer_entry.timestamp, TS}]) + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + #exometer_entry{module = ?MODULE, type = gauge} -> + TS = exometer_util:timestamp(), + [ets:update_element(T, Name, [{#exometer_entry.value, 0}, + {#exometer_entry.timestamp, TS}]) + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + #exometer_entry{behaviour = probe, type = Type, ref = Ref} -> + [exometer_cache:delete(Name, DataPoint) || + DataPoint <- exometer_util:get_datapoints(E)], + exometer_probe:reset(Name, Type, Ref), + ok; + #exometer_entry{behaviour = entry, + module = M, type = Type, ref = Ref} -> + [exometer_cache:delete(Name, DataPoint) || + DataPoint <- exometer_util:get_datapoints(E)], + M:reset(Name, Type, Ref) + end; + [] -> {error, not_found}; - _ -> - ok + _ -> + ok end. @@ -490,29 +490,29 @@ setopts(Name, Options) when is_list(Name), is_list(Options) -> [#exometer_entry{module = ?MODULE, type = Type, status = Status} = E] -> - if ?IS_DISABLED(Status) -> + if ?IS_DISABLED(Status) -> case lists:keyfind(status, 1, Options) of {_, enabled} -> - case Type of - fast_counter -> setopts_fctr(E, Options); - counter -> setopts_ctr(E, Options); - gauge -> setopts_gauge(E, Options) + case Type of + fast_counter -> setopts_fctr(E, Options); + counter -> setopts_ctr(E, Options); + gauge -> setopts_gauge(E, Options) end, reporter_setopts(E, Options, enabled); _ -> {error, disabled} end; - ?IS_ENABLED(Status) -> + ?IS_ENABLED(Status) -> case lists:keyfind(status, 1, Options) of {_, disabled} -> {_, Elems} = process_setopts(E, Options), update_entry_elems(Name, Elems), reporter_setopts(E, Options, disabled); R when R==false; R=={status,disabled} -> - case Type of - fast_counter -> setopts_fctr(E, Options); - counter -> setopts_ctr(E, Options); - gauge -> setopts_gauge(E, Options) + case Type of + fast_counter -> setopts_fctr(E, Options); + counter -> setopts_ctr(E, Options); + gauge -> setopts_gauge(E, Options) end, reporter_setopts(E, Options, enabled) end @@ -541,7 +541,7 @@ module_setopts(#exometer_entry{behaviour = probe}=E, Options, NewStatus) -> exometer_probe:setopts(E, Options, NewStatus); module_setopts(#exometer_entry{behaviour = entry, - module=M} = E, Options, NewStatus) -> + module=M} = E, Options, NewStatus) -> case [O || {K, _} = O <- Options, not lists:member(K, [status, cache, ref])] of [] -> @@ -626,25 +626,25 @@ info(#exometer_entry{} = E, Item) -> info(Name, Item) -> case ets:lookup(exometer_util:table(), Name) of [#exometer_entry{} = E] -> - info_(E, Item); + info_(E, Item); _ -> undefined end. info_(E, Item) -> case Item of - name -> E#exometer_entry.name; - type -> E#exometer_entry.type; - module -> E#exometer_entry.module; - value -> get_value_(E,[]); - cache -> E#exometer_entry.cache; - status -> info_status(E#exometer_entry.status); - timestamp -> E#exometer_entry.timestamp; - options -> E#exometer_entry.options; - ref -> E#exometer_entry.ref; - entry -> E; - datapoints-> exometer_util:get_datapoints(E); - _ -> undefined + name -> E#exometer_entry.name; + type -> E#exometer_entry.type; + module -> E#exometer_entry.module; + value -> get_value_(E,[]); + cache -> E#exometer_entry.cache; + status -> info_status(E#exometer_entry.status); + timestamp -> E#exometer_entry.timestamp; + options -> E#exometer_entry.options; + ref -> E#exometer_entry.ref; + entry -> E; + datapoints-> exometer_util:get_datapoints(E); + _ -> undefined end. info_status(S) when ?IS_ENABLED(S) -> @@ -666,12 +666,12 @@ datapoints(D, _) when is_list(D) -> info(Name) -> case ets:lookup(exometer_util:table(), Name) of [#exometer_entry{} = E0] -> - E = info_set_status(E0), + E = info_set_status(E0), Flds = record_info(fields, exometer_entry), lists:keyreplace(value, 1, lists:zip(Flds, tl(tuple_to_list(E))) ++ [ {datapoints, - exometer_util:get_datapoints(E)}], + exometer_util:get_datapoints(E)}], {value, get_value_(E, [])}); _ -> undefined @@ -699,8 +699,8 @@ find_entries(Path) -> [ { #exometer_entry{name = Pat, _ = '_'}, [], [{{ {element, #exometer_entry.name, '$_'}, {element, #exometer_entry.type, '$_'}, - status_body_pattern() - }}] } ]). + status_body_pattern() + }}] } ]). get_values(Path) -> Entries = find_entries(Path), @@ -781,9 +781,9 @@ select_cont(Cont) -> %% @end aggregate(Pattern, DataPoints) -> case aggr_select(Pattern) of - [] -> []; - Found -> - aggregate(Found, DataPoints, orddict:new()) + [] -> []; + Found -> + aggregate(Found, DataPoints, orddict:new()) end. aggr_select(Pattern) -> @@ -791,21 +791,21 @@ aggr_select(Pattern) -> aggregate([N|Ns], DPs, Acc) when is_list(N) -> case get_value(N, DPs) of - {ok, Vals} -> - aggregate(Ns, DPs, aggr_acc(Vals, Acc)); - _ -> - aggregate(Ns, DPs, Acc) + {ok, Vals} -> + aggregate(Ns, DPs, aggr_acc(Vals, Acc)); + _ -> + aggregate(Ns, DPs, Acc) end; aggregate([], _, Acc) -> Acc. aggr_acc([{D,V}|T], Acc) -> if is_integer(V) -> - aggr_acc(T, orddict:update(D, fun(Val) -> - Val + V - end, V, Acc)); + aggr_acc(T, orddict:update(D, fun(Val) -> + Val + V + end, V, Acc)); true -> - aggr_acc(T, Acc) + aggr_acc(T, Acc) end; aggr_acc([], Acc) -> Acc. @@ -864,27 +864,27 @@ g_subst(Ks) -> %% Potentially save Name, Type and Status match head patterns match_tail(N,T,S) -> case is_dollar(N) of true -> - [{N, {element,#exometer_entry.name,'$_'}}|match_tail(T,S)]; - false -> match_tail(T, S) + [{N, {element,#exometer_entry.name,'$_'}}|match_tail(T,S)]; + false -> match_tail(T, S) end. match_tail(T,S) -> case is_dollar(T) of true -> - [{T, {element,#exometer_entry.type,'$_'}}|match_tail(S)]; - false -> match_tail(S) + [{T, {element,#exometer_entry.type,'$_'}}|match_tail(S)]; + false -> match_tail(S) end. match_tail(S) -> case is_dollar(S) of true -> - [{S, status_element_pattern(S)}]; - false -> [] + [{S, status_element_pattern(S)}]; + false -> [] end. is_dollar(A) when is_atom(A) -> case atom_to_list(A) of - [$$ | Rest] -> - try _ = list_to_integer(Rest), true - catch error:_ -> false - end; - _ -> false + [$$ | Rest] -> + try _ = list_to_integer(Rest), true + catch error:_ -> false + end; + _ -> false end; is_dollar(_) -> false. @@ -892,8 +892,8 @@ is_dollar(_) -> false. g_subst_({_,_}=X) -> X; g_subst_(K) when is_atom(K) -> {K, {{{element,#exometer_entry.name,'$_'}, - {element,#exometer_entry.type,'$_'}, - status_element_pattern({element,#exometer_entry.status,'$_'})}}}. + {element,#exometer_entry.type,'$_'}, + status_element_pattern({element,#exometer_entry.status,'$_'})}}}. %% The status attribute: bit 1 indicates enabled (1) or disabled (0) @@ -904,7 +904,7 @@ status_element_pattern(S) -> status_body_pattern() -> {element, {'+',{'band', - {element,#exometer_entry.status,'$_'},1},1}, + {element,#exometer_entry.status,'$_'},1},1}, {{disabled,enabled}}}. p_subst(Ks) -> @@ -913,7 +913,7 @@ p_subst_({_,_}=X) -> X; p_subst_(K) when is_atom(K) -> {K, {{{element,#exometer_entry.name,'$_'}, {element,#exometer_entry.type,'$_'}, - status_body_pattern()}}}. + status_body_pattern()}}}. %% This function returns a list of elements for ets:update_element/3, %% to be used for updating the #exometer_entry{} instances. @@ -937,48 +937,48 @@ process_setopts_(#exometer_entry{options = OldOpts} = Entry, Options) -> fun ({cache, Val}, {#exometer_entry{cache = Cache0} = E, Elems} = Acc) -> - if is_integer(Val), Val >= 0 -> - if Val =/= Cache0 -> - {E#exometer_entry{cache = Val}, - add_elem(cache, Val, Elems)}; - true -> - Acc - end; - true -> error({illegal, {cache, Val}}) - end; + if is_integer(Val), Val >= 0 -> + if Val =/= Cache0 -> + {E#exometer_entry{cache = Val}, + add_elem(cache, Val, Elems)}; + true -> + Acc + end; + true -> error({illegal, {cache, Val}}) + end; ({status, Status}, {#exometer_entry{status = Status0} = E, Elems} = Acc) -> - case is_status_change(Status, Status0) of - {true, Status1} -> - {E#exometer_entry{status = Status1}, - add_elem(status, Status1, Elems)}; - false -> - Acc - end; - ({update_event, UE}, - {#exometer_entry{status = Status0} = E, Elems} = Acc) - when is_boolean(UE) -> - case (exometer_util:test_event_flag(update, Status0) == UE) of - true -> Acc; - false -> - %% value changed - if UE -> - Status = exometer_util:set_event_flag( - update, E#exometer_entry.status), - {E#exometer_entry{status = Status}, - add_elem(status, Status, Elems)}; - true -> - Status = exometer_util:clear_event_flag( - update, E#exometer_entry.status), - {E#exometer_entry{status = Status}, - add_elem(status, Status, Elems)} - end - end; - ({ref,R}, {E, Elems}) -> - {E#exometer_entry{ref = R}, add_elem(ref, R, Elems)}; + case is_status_change(Status, Status0) of + {true, Status1} -> + {E#exometer_entry{status = Status1}, + add_elem(status, Status1, Elems)}; + false -> + Acc + end; + ({update_event, UE}, + {#exometer_entry{status = Status0} = E, Elems} = Acc) + when is_boolean(UE) -> + case (exometer_util:test_event_flag(update, Status0) == UE) of + true -> Acc; + false -> + %% value changed + if UE -> + Status = exometer_util:set_event_flag( + update, E#exometer_entry.status), + {E#exometer_entry{status = Status}, + add_elem(status, Status, Elems)}; + true -> + Status = exometer_util:clear_event_flag( + update, E#exometer_entry.status), + {E#exometer_entry{status = Status}, + add_elem(status, Status, Elems)} + end + end; + ({ref,R}, {E, Elems}) -> + {E#exometer_entry{ref = R}, add_elem(ref, R, Elems)}; ({_,_}, Acc) -> - Acc - end, {Entry, []}, Options), - {E1, [{#exometer_entry.options, update_opts(Options, OldOpts)}|Elems]}. + Acc + end, {Entry, []}, Options), + {E1, [{#exometer_entry.options, update_opts(Options, OldOpts)}|Elems]}. is_status_change(enabled, St) -> if ?IS_ENABLED(St) -> false; @@ -1021,7 +1021,7 @@ type_arg_first(Opts) -> %% the exometer record itself. get_ctr_datapoint(#exometer_entry{name = Name}, value) -> {value, lists:sum([ets:lookup_element(T, Name, #exometer_entry.value) - || T <- exometer_util:tables()])}; + || T <- exometer_util:tables()])}; get_ctr_datapoint(#exometer_entry{timestamp = TS}, ms_since_reset) -> {ms_since_reset, exometer_util:timestamp() - TS}; get_ctr_datapoint(#exometer_entry{}, Undefined) -> @@ -1054,7 +1054,7 @@ get_fctr_datapoint(#exometer_entry{ }, Undefined) -> create_entry(#exometer_entry{module = exometer, type = Type} = E) when Type == counter; - Type == gauge -> + Type == gauge -> E1 = E#exometer_entry{value = 0, timestamp = exometer_util:timestamp()}, insert_aliases(E1), [ets:insert(T, E1) || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], @@ -1084,21 +1084,21 @@ create_entry(#exometer_entry{module = exometer, create_entry(#exometer_entry{module = Module, type = Type, name = Name, - options = Opts} = E) -> + options = Opts} = E) -> case - case Module:behaviour() of - probe -> - {probe, exometer_probe:new(Name, Type, [{ arg, Module} | Opts ]) }; - entry -> - {entry, Module:new(Name, Type, Opts) }; - - Other -> Other - end + case Module:behaviour() of + probe -> + {probe, exometer_probe:new(Name, Type, [{ arg, Module} | Opts ]) }; + entry -> + {entry, Module:new(Name, Type, Opts) }; + + Other -> Other + end of {Behaviour, ok }-> insert_aliases(E), [ets:insert(T, E#exometer_entry { behaviour = Behaviour }) - || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], ok; {Behaviour, {ok, Ref}} -> insert_aliases(E), @@ -1140,10 +1140,10 @@ set_call_count(M, F, Bool) when is_atom(M), is_atom(F), is_boolean(Bool) -> %% register_application() -> case application:get_application() of - {ok, App} -> - register_application(App); - Other -> - {error, Other} + {ok, App} -> + register_application(App); + Other -> + {error, Other} end. -spec register_application(_Application::atom()) -> ok | error(). diff --git a/src/exometer_admin.erl b/src/exometer_admin.erl index cbe361e..29f3d4c 100644 --- a/src/exometer_admin.erl +++ b/src/exometer_admin.erl @@ -99,20 +99,20 @@ load_predefined() -> register_application(App) -> %% Ignore if exometer is not running case whereis(exometer_admin) of - undefined -> ok; - _ -> - case setup:get_env(App, exometer_defaults) of - {ok, E} -> - do_load_defaults(App, get_predef(E)); - undefined -> - ok - end, - case setup:get_env(App, exometer_predefined) of - {ok, P} -> - do_load_predef(App, get_predef(P)); - undefined -> - ok - end + undefined -> ok; + _ -> + case setup:get_env(App, exometer_defaults) of + {ok, E} -> + do_load_defaults(App, get_predef(E)); + undefined -> + ok + end, + case setup:get_env(App, exometer_predefined) of + {ok, P} -> + do_load_predef(App, get_predef(P)); + undefined -> + ok + end end. get_predef({script, F} ) -> ok(file:script(F, []), F); @@ -124,36 +124,36 @@ do_load_defaults(Src, L) when is_list(L) -> lists:foreach( fun({NamePattern, Type, Spec}) -> try set_default(NamePattern, Type, Spec) - catch - error:E -> - lager:error("Defaults(~p): ERROR: ~p~n", [Src, E]) - end + catch + error:E -> + lager:error("Defaults(~p): ERROR: ~p~n", [Src, E]) + end end, L). do_load_predef(Src, L) when is_list(L) -> lists:foreach( fun({Name, Type, Options}) -> new_entry(Name, Type, Options); - ({delete, Key}) -> - predef_delete_entry(Key, Src); - ({re_register, {Name, Type, Options}}) -> - re_register_entry(Name, Type, Options); - ({select_delete, Pat}) -> - Found = exometer:select(Pat), - lists:foreach( - fun({K,_,_}) -> - predef_delete_entry(K, Src); - (Other) -> - lager:error("Predef(~p): ~p~n", - [Src, {bad_pattern,Other}]) - end, Found) + ({delete, Key}) -> + predef_delete_entry(Key, Src); + ({re_register, {Name, Type, Options}}) -> + re_register_entry(Name, Type, Options); + ({select_delete, Pat}) -> + Found = exometer:select(Pat), + lists:foreach( + fun({K,_,_}) -> + predef_delete_entry(K, Src); + (Other) -> + lager:error("Predef(~p): ~p~n", + [Src, {bad_pattern,Other}]) + end, Found) end, L). predef_delete_entry(Key, Src) -> case delete_entry(Key) of - ok -> ok; - Error -> - lager:error("Predef(~p): ~p~n", [Src, Error]) + ok -> ok; + Error -> + lager:error("Predef(~p): ~p~n", [Src, Error]) end. ok({ok, Res}, _) -> Res; @@ -189,10 +189,10 @@ re_register_entry(Name, Type, Opts) -> repair_entry(Name) -> case gen_server:call(?MODULE, {repair_entry, Name}) of - {error, Reason} -> - error(Reason); - ok -> - ok + {error, Reason} -> + error(Reason); + ok -> + ok end. delete_entry(Name) -> @@ -201,10 +201,10 @@ delete_entry(Name) -> ensure(Name, Type, Opts) -> {Type1, Opt1} = check_type_arg(Type, Opts), case gen_server:call(?MODULE, {ensure, Name, Type1, Opt1}) of - {error, Reason} -> - error(Reason); - ok -> - ok + {error, Reason} -> + error(Reason); + ok -> + ok end. auto_create_entry(Name) -> @@ -230,9 +230,9 @@ demonitor(Pid) when is_pid(Pid) -> opts_to_rec(Type, Opts0) -> Opts = case lists:keymember(module, 1, Opts0) of - true -> Opts0; - false -> [{module, module(Type)}|Opts0] - end, + true -> Opts0; + false -> [{module, module(Type)}|Opts0] + end, Flds = record_info(fields, exometer_entry), lists:foldr(fun({K,V}, Acc) -> setelement(pos(K, Flds), Acc, V) @@ -276,49 +276,49 @@ handle_call({new_entry, Name, Type, Opts, AllowExisting}, _From, S) -> end; handle_call({repair_entry, Name}, _From, S) -> try - #exometer_entry{} = E = exometer:info(Name, entry), - delete_entry_(Name), - exometer:create_entry(E), - {reply, ok, S} + #exometer_entry{} = E = exometer:info(Name, entry), + delete_entry_(Name), + exometer:create_entry(E), + {reply, ok, S} catch - error:Error -> - {reply, {error, Error}, S} + error:Error -> + {reply, {error, Error}, S} end; handle_call({propose, Name, Type, Opts}, _From, S) -> try - #exometer_entry{options = NewOpts} = E0 = - lookup_definition(Name, Type, Opts), - E1 = process_opts(E0, NewOpts), - {reply, exometer_info:pp(E1), S} + #exometer_entry{options = NewOpts} = E0 = + lookup_definition(Name, Type, Opts), + E1 = process_opts(E0, NewOpts), + {reply, exometer_info:pp(E1), S} catch - error:Error -> - {reply, {error, Error}, S} + error:Error -> + {reply, {error, Error}, S} end; handle_call({delete_entry, Name}, _From, S) -> {reply, delete_entry_(Name), S}; handle_call({ensure, Name, Type, Opts}, _From, S) -> case ets:lookup(exometer_util:table(), Name) of - [#exometer_entry{type = Type}] -> - {reply, ok, S}; - [#exometer_entry{type = _OtherType}] -> - {reply, {error, type_mismatch}, S}; - [] -> - #exometer_entry{options = OptsTemplate} = E0 = - lookup_definition(Name, Type, Opts), - E1 = process_opts(E0, OptsTemplate ++ Opts), - Res = exometer:create_entry(E1), - report_new_entry(E1), - {reply, Res, S} + [#exometer_entry{type = Type}] -> + {reply, ok, S}; + [#exometer_entry{type = _OtherType}] -> + {reply, {error, type_mismatch}, S}; + [] -> + #exometer_entry{options = OptsTemplate} = E0 = + lookup_definition(Name, Type, Opts), + E1 = process_opts(E0, OptsTemplate ++ Opts), + Res = exometer:create_entry(E1), + report_new_entry(E1), + {reply, Res, S} end; handle_call({auto_create, Name}, _From, S) -> case find_auto_template(Name) of - false -> - {reply, {error, no_template}, S}; - #exometer_entry{options = Opts} = E -> - E1 = process_opts(E#exometer_entry{name = Name}, Opts), - Res = exometer:create_entry(E1), - report_new_entry(E1), - {reply, Res, S} + false -> + {reply, {error, no_template}, S}; + #exometer_entry{options = Opts} = E -> + E1 = process_opts(E#exometer_entry{name = Name}, Opts), + Res = exometer:create_entry(E1), + report_new_entry(E1), + {reply, Res, S} end; handle_call(_, _, S) -> {reply, error, S}. @@ -363,17 +363,17 @@ terminate(_, _) -> code_change(_, S, _) -> case ets:info(?EXOMETER_REPORTERS, name) of - undefined -> create_reporter_tabs(); - _ -> ok + undefined -> create_reporter_tabs(); + _ -> ok end, {ok, S}. create_reporter_tabs() -> Heir = {heir, whereis(exometer_sup), []}, ets:new(?EXOMETER_REPORTERS, [public, named_table, set, - {keypos, 2}, Heir]), + {keypos, 2}, Heir]), ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set, - {keypos, 2}, Heir]). + {keypos, 2}, Heir]). create_ets_tabs() -> @@ -385,10 +385,10 @@ create_ets_tabs() -> {keypos, 2}]), ets:new(?EXOMETER_ENTRIES, [public, named_table, ordered_set, {keypos, 2}]), - ets:new(?EXOMETER_REPORTERS, [public, named_table, set, - {keypos, 2}]), - ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set, - {keypos, 2}]); + ets:new(?EXOMETER_REPORTERS, [public, named_table, set, + {keypos, 2}]), + ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set, + {keypos, 2}]); _ -> true end. @@ -423,36 +423,36 @@ lookup_definition(Name, ad_hoc, Opts) -> end; lookup_definition(Name, Type, Opts) -> E = case ets:prev(?EXOMETER_SHARED, {default, Type, <<>>}) of - {default, Type, N} = D0 when N==[''], N==Name -> - case ets:lookup(?EXOMETER_SHARED, D0) of - [#exometer_entry{} = Def] -> - Def; - [] -> - default_definition_(Name, Type) - end; - {default, OtherType, _} when Type=/=OtherType -> - exometer_default(Name, Type); - '$end_of_table' -> - exometer_default(Name, Type); - _ -> - default_definition_(Name, Type) - end, + {default, Type, N} = D0 when N==[''], N==Name -> + case ets:lookup(?EXOMETER_SHARED, D0) of + [#exometer_entry{} = Def] -> + Def; + [] -> + default_definition_(Name, Type) + end; + {default, OtherType, _} when Type=/=OtherType -> + exometer_default(Name, Type); + '$end_of_table' -> + exometer_default(Name, Type); + _ -> + default_definition_(Name, Type) + end, merge_opts(Opts, E). merge_opts(Opts, #exometer_entry{options = DefOpts} = E) -> Opts1 = lists:foldl(fun({'--', Keys}, Acc) -> - case is_list(Keys) of - true -> - lists:foldl( - fun(K,Acc1) -> - lists:keydelete(K, 1, Acc1) - end, Acc, Keys); - false -> - error({invalid_option,'--'}) - end; - ({K,V}, Acc) -> - lists:keystore(K, 1, Acc, {K,V}) - end, DefOpts, Opts), + case is_list(Keys) of + true -> + lists:foldl( + fun(K,Acc1) -> + lists:keydelete(K, 1, Acc1) + end, Acc, Keys); + false -> + error({invalid_option,'--'}) + end; + ({K,V}, Acc) -> + lists:keystore(K, 1, Acc, {K,V}) + end, DefOpts, Opts), E#exometer_entry{options = Opts1}. default_definition_(Name, Type) -> @@ -494,11 +494,11 @@ search_default(Name, Type) -> sort_defaults(L) -> lists:sort(fun comp_templates/2, - [E || #exometer_entry{type = T} = E <- L, - T =/= function andalso T =/= cpu]). + [E || #exometer_entry{type = T} = E <- L, + T =/= function andalso T =/= cpu]). comp_templates(#exometer_entry{name = {default, _, A}, type = Ta}, - #exometer_entry{name = {default, _, B}, type = Tb}) -> + #exometer_entry{name = {default, _, B}, type = Tb}) -> comp_names(A, B, Ta, Tb). @@ -521,10 +521,10 @@ comp_types(A, B) -> A =< B. %% @end find_auto_template(Name) -> case sort_defaults(ets:select(?EXOMETER_SHARED, - make_patterns('_', Name))) of - [] -> false; - [#exometer_entry{name = {default,_,['']}}|_] -> false; - [#exometer_entry{} = E|_] -> E + make_patterns('_', Name))) of + [] -> false; + [#exometer_entry{name = {default,_,['']}}|_] -> false; + [#exometer_entry{} = E|_] -> E end. make_patterns(Type, Name) when is_list(Name) -> @@ -568,32 +568,32 @@ process_opts(Entry, Options) -> %% Unknown option, pass on to exometer entry options list, replacing %% any earlier versions of the same option. ({cache, Val}, E) -> - if is_integer(Val), Val >= 0 -> - E#exometer_entry{cache = Val}; - true -> - error({illegal, {cache, Val}}) - end; + if is_integer(Val), Val >= 0 -> + E#exometer_entry{cache = Val}; + true -> + error({illegal, {cache, Val}}) + end; ({status, Status}, #exometer_entry{} = E) -> - if Status==enabled; Status==disabled -> - Status1 = exometer_util:set_status( - Status, Entry#exometer_entry.status), - E#exometer_entry{status = Status1}; - true -> - error({illegal, {status, Status}}) - end; - ({update_event, UE}, #exometer_entry{} = E) when is_boolean(UE) -> - if UE -> - Status = exometer_util:set_event_flag( - update, E#exometer_entry.status), - E#exometer_entry{status = Status}; - true -> - Status = exometer_util:clear_event_flag( - update, E#exometer_entry.status), - E#exometer_entry{status = Status} - end; + if Status==enabled; Status==disabled -> + Status1 = exometer_util:set_status( + Status, Entry#exometer_entry.status), + E#exometer_entry{status = Status1}; + true -> + error({illegal, {status, Status}}) + end; + ({update_event, UE}, #exometer_entry{} = E) when is_boolean(UE) -> + if UE -> + Status = exometer_util:set_event_flag( + update, E#exometer_entry.status), + E#exometer_entry{status = Status}; + true -> + Status = exometer_util:clear_event_flag( + update, E#exometer_entry.status), + E#exometer_entry{status = Status} + end; ({_Opt, _Val}, #exometer_entry{} = Entry1) -> - Entry1 - end, Entry#exometer_entry{options = Options}, Options). + Entry1 + end, Entry#exometer_entry{options = Options}, Options). delete_entry_(Name) -> case ets:lookup(exometer_util:table(), Name) of @@ -604,34 +604,34 @@ delete_entry_(Name) -> ok; [#exometer_entry{module = exometer, type = fast_counter, ref = {M, F}}] -> - try - exometer_util:set_call_count(M, F, false) - after - [ets:delete(T, Name) || - T <- [?EXOMETER_ENTRIES|exometer_util:tables()]] - end, + try + exometer_util:set_call_count(M, F, false) + after + [ets:delete(T, Name) || + T <- [?EXOMETER_ENTRIES|exometer_util:tables()]] + end, ok; [#exometer_entry{behaviour = probe, - type = Type, ref = Ref} = E] -> - [ exometer_cache:delete(Name, DataPoint) || - DataPoint <- exometer_util:get_datapoints(E)], - try - exometer_probe:delete(Name, Type, Ref) - after - [ets:delete(T, Name) || - T <- [?EXOMETER_ENTRIES|exometer_util:tables()]] - end, - ok; + type = Type, ref = Ref} = E] -> + [ exometer_cache:delete(Name, DataPoint) || + DataPoint <- exometer_util:get_datapoints(E)], + try + exometer_probe:delete(Name, Type, Ref) + after + [ets:delete(T, Name) || + T <- [?EXOMETER_ENTRIES|exometer_util:tables()]] + end, + ok; [#exometer_entry{module= Mod, behaviour = entry, - type = Type, ref = Ref} = E] -> - [ exometer_cache:delete(Name, DataPoint) || - DataPoint <- exometer_util:get_datapoints(E)], + type = Type, ref = Ref} = E] -> + [ exometer_cache:delete(Name, DataPoint) || + DataPoint <- exometer_util:get_datapoints(E)], try Mod:delete(Name, Type, Ref) after [ets:delete(T, Name) || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]] end, - ok; + ok; [] -> {error, not_found} end. diff --git a/src/exometer_alias.erl b/src/exometer_alias.erl index 7ddd3e7..20b7dd5 100644 --- a/src/exometer_alias.erl +++ b/src/exometer_alias.erl @@ -23,26 +23,26 @@ -behaviour(gen_server). -export([new/3, - load/1, - unload/1, - delete/1, - update/2, - resolve/1, + load/1, + unload/1, + delete/1, + update/2, + resolve/1, reverse_map/2, - get_value/1, - prefix_match/1, - prefix_foldl/3, - prefix_foldr/3, - regexp_foldl/3, - regexp_foldr/3]). + get_value/1, + prefix_match/1, + prefix_foldl/3, + prefix_foldr/3, + regexp_foldl/3, + regexp_foldr/3]). -export([start_link/0, - init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2, - code_change/3]). + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). -define(TAB, ?MODULE). -define(COMPILED_RE(P), is_tuple(P), element(1, P) == re_pattern). @@ -122,10 +122,10 @@ delete(Alias) -> resolve(Alias) -> Key = to_key(Alias), case ets_lookup(Key) of - [#alias{entry = Entry, dp = DP}] -> - {Entry, DP}; - [] -> - error + [#alias{entry = Entry, dp = DP}] -> + {Entry, DP}; + [] -> + error end. -spec reverse_map(name() | '_', dp() | '_') -> [{alias(),name(),dp()}]. @@ -149,15 +149,15 @@ reverse_map(Name, Datapoint) -> %% @end get_value(Alias) -> case resolve(Alias) of - {Entry, DP} -> - case exometer:get_value(Entry, [DP]) of - {ok, [{_, Value}]} -> - {ok, Value}; - Error -> - Error - end; - error -> - {error, not_found} + {Entry, DP} -> + case exometer:get_value(Entry, [DP]) of + {ok, [{_, Value}]} -> + {ok, Value}; + Error -> + Error + end; + error -> + {error, not_found} end. -spec update(alias(), any()) -> ok | {error, any()}. @@ -169,10 +169,10 @@ get_value(Alias) -> %% @end update(Alias, Value) -> case resolve(Alias) of - {Entry, _} -> - exometer:update(Entry, Value); - error -> - {error, not_found} + {Entry, _} -> + exometer:update(Entry, Value); + error -> + {error, not_found} end. -spec prefix_match(binary()) -> [{alias(), name(), dp()}]. @@ -192,28 +192,28 @@ prefix_match(Pattern) when is_binary(Pattern) -> %% @end prefix_foldl(Prefix, F, Acc) -> case ets_lookup(Prefix) of - [] -> - prefix_foldl(ets_next(Prefix), Prefix, byte_size(Prefix), - F, Acc); - [#alias{key = Key}] -> - prefix_foldl(Key, Prefix, byte_size(Prefix), F, Acc) + [] -> + prefix_foldl(ets_next(Prefix), Prefix, byte_size(Prefix), + F, Acc); + [#alias{key = Key}] -> + prefix_foldl(Key, Prefix, byte_size(Prefix), F, Acc) end. prefix_foldl('$end_of_table', _, _, _, Acc) -> Acc; prefix_foldl(Key, Pattern, Sz, F, Acc) -> case Key of - <> -> - case ets_lookup(Key) of - [#alias{alias = Alias, entry = E, dp = DP}] -> - prefix_foldl(ets_next(Key), - Pattern, Sz, F, - F(Alias, E, DP, Acc)); - _ -> - prefix_foldl(ets_next(Key), Pattern, Sz, F, Acc) - end; - _ -> - Acc + <> -> + case ets_lookup(Key) of + [#alias{alias = Alias, entry = E, dp = DP}] -> + prefix_foldl(ets_next(Key), + Pattern, Sz, F, + F(Alias, E, DP, Acc)); + _ -> + prefix_foldl(ets_next(Key), Pattern, Sz, F, Acc) + end; + _ -> + Acc end. -spec prefix_foldr(binary(), fold_fun(), acc()) -> acc(). @@ -224,27 +224,27 @@ prefix_foldl(Key, Pattern, Sz, F, Acc) -> %% @end prefix_foldr(Pattern, F, Acc) -> case ets_lookup(Pattern) of - [] -> - prefix_foldr(ets_next(Pattern), Pattern, byte_size(Pattern), - F, Acc); - [#alias{key = Key}] -> - prefix_foldr(Key, Pattern, byte_size(Pattern), F, Acc) + [] -> + prefix_foldr(ets_next(Pattern), Pattern, byte_size(Pattern), + F, Acc); + [#alias{key = Key}] -> + prefix_foldr(Key, Pattern, byte_size(Pattern), F, Acc) end. prefix_foldr('$end_of_table', _, _, _, Acc) -> Acc; prefix_foldr(Key, Pattern, Sz, F, Acc) -> case Key of - <> -> - case ets_lookup(Key) of - [#alias{alias = Alias, entry = E, dp = DP}] -> - F(Alias, E, DP, prefix_foldr(ets_next(Key), - Pattern, Sz, F, Acc)); - _ -> - prefix_foldr(ets_next(Key), Pattern, Sz, F, Acc) - end; - _ -> - Acc + <> -> + case ets_lookup(Key) of + [#alias{alias = Alias, entry = E, dp = DP}] -> + F(Alias, E, DP, prefix_foldr(ets_next(Key), + Pattern, Sz, F, Acc)); + _ -> + prefix_foldr(ets_next(Key), Pattern, Sz, F, Acc) + end; + _ -> + Acc end. -spec regexp_foldl(regexp(), fold_fun(), acc()) -> acc(). @@ -262,35 +262,35 @@ regexp_foldl(Regexp, F, Acc) when ?COMPILED_RE(Regexp) -> regexp_foldl(Regexp, F, Acc) -> Prefix = regexp_prefix(Regexp), case ets_lookup(Prefix) of - [] -> - regexp_foldl(ets_next(Prefix), Prefix, byte_size(Prefix), - re_compile(Regexp), F, Acc); - [#alias{key = Key}] -> - regexp_foldl(Key, Prefix, byte_size(Prefix), - re_compile(Regexp), F, Acc) + [] -> + regexp_foldl(ets_next(Prefix), Prefix, byte_size(Prefix), + re_compile(Regexp), F, Acc); + [#alias{key = Key}] -> + regexp_foldl(Key, Prefix, byte_size(Prefix), + re_compile(Regexp), F, Acc) end. regexp_foldl('$end_of_table', _, _, _, _, Acc) -> Acc; regexp_foldl(Key, Prefix, Sz, Pattern, F, Acc) -> case Key of - <> -> - case re:run(Key, Pattern) of - {match, _} -> - case ets_lookup(Key) of - [#alias{alias = Alias, entry = E, dp = DP}] -> - regexp_foldl(ets_next(Key), Prefix, Sz, - Pattern, F, F(Alias, E, DP, Acc)); - _ -> - regexp_foldl(ets_next(Key), - Prefix, Sz, Pattern, F, Acc) - end; - nomatch -> - regexp_foldl(ets_next(Key), - Prefix, Sz, Pattern, F, Acc) - end; - _ -> - Acc + <> -> + case re:run(Key, Pattern) of + {match, _} -> + case ets_lookup(Key) of + [#alias{alias = Alias, entry = E, dp = DP}] -> + regexp_foldl(ets_next(Key), Prefix, Sz, + Pattern, F, F(Alias, E, DP, Acc)); + _ -> + regexp_foldl(ets_next(Key), + Prefix, Sz, Pattern, F, Acc) + end; + nomatch -> + regexp_foldl(ets_next(Key), + Prefix, Sz, Pattern, F, Acc) + end; + _ -> + Acc end. -spec regexp_foldr(regexp(), fold_fun(), acc()) -> acc(). @@ -308,37 +308,37 @@ regexp_foldr(Pattern, F, Acc) when ?COMPILED_RE(Pattern) -> regexp_foldr(Pattern, F, Acc) -> Prefix = regexp_prefix(Pattern), case ets_lookup(Prefix) of - [] -> - regexp_foldr(ets_next(Prefix), Prefix, byte_size(Prefix), - re_compile(Pattern), - F, Acc); - [#alias{key = Key}] -> - regexp_foldr(Key, Prefix, byte_size(Prefix), - re_compile(Pattern), F, Acc) + [] -> + regexp_foldr(ets_next(Prefix), Prefix, byte_size(Prefix), + re_compile(Pattern), + F, Acc); + [#alias{key = Key}] -> + regexp_foldr(Key, Prefix, byte_size(Prefix), + re_compile(Pattern), F, Acc) end. regexp_foldr('$end_of_table', _, _, _, _, Acc) -> Acc; regexp_foldr(Key, Prefix, Sz, Pattern, F, Acc) -> case Key of - <> -> - case re:run(Key, Pattern) of - {match, _} -> - case ets_lookup(Key) of - [#alias{alias = Alias, entry = E, dp = DP}] -> - F(Alias, E, DP, regexp_foldr(ets_next(Key), - Prefix, Sz, Pattern, - F, Acc)); - _ -> - regexp_foldr(ets_next(Key), Prefix, Sz, - Pattern, F, Acc) - end; - nomatch -> - regexp_foldr(ets_next(Key), Prefix, Sz, - Pattern, F, Acc) - end; - _ -> - Acc + <> -> + case re:run(Key, Pattern) of + {match, _} -> + case ets_lookup(Key) of + [#alias{alias = Alias, entry = E, dp = DP}] -> + F(Alias, E, DP, regexp_foldr(ets_next(Key), + Prefix, Sz, Pattern, + F, Acc)); + _ -> + regexp_foldr(ets_next(Key), Prefix, Sz, + Pattern, F, Acc) + end; + nomatch -> + regexp_foldr(ets_next(Key), Prefix, Sz, + Pattern, F, Acc) + end; + _ -> + Acc end. just_acc(Alias, Entry, DP, Acc) -> @@ -347,11 +347,11 @@ just_acc(Alias, Entry, DP, Acc) -> start_link() -> Tab = maybe_create_ets(), case gen_server:start_link({local, ?MODULE}, ?MODULE, [], []) of - {ok, Pid} = Res -> - ets:give_away(Tab, Pid, give_away), - Res; - Other -> - Other + {ok, Pid} = Res -> + ets:give_away(Tab, Pid, give_away), + Res; + Other -> + Other end. %% @private @@ -362,25 +362,25 @@ init(_) -> handle_call({new, Alias, Entry, DP}, _, St) -> Key = to_key(Alias), Res = case ets:member(?TAB, Key) of - true -> - {error, exists}; - false -> - ets:insert(?TAB, #alias{key = Key, alias = Alias, - entry = Entry, dp = DP}), - ok - end, + true -> + {error, exists}; + false -> + ets:insert(?TAB, #alias{key = Key, alias = Alias, + entry = Entry, dp = DP}), + ok + end, {reply, Res, St}; handle_call({load, F}, _, St) -> Res = try do_load(F) - catch - error:R -> {error, R} - end, + catch + error:R -> {error, R} + end, {reply, Res, St}; handle_call({unload, F}, _, St) -> Res = try do_unload(F) - catch - error:R -> {error, R} - end, + catch + error:R -> {error, R} + end, {reply, Res, St}; handle_call({delete, Alias}, _, St) -> Key = to_key(Alias), @@ -409,11 +409,11 @@ code_change(_, St, _) -> maybe_create_ets() -> case ets:info(?TAB, name) of - undefined -> - ets:new(?TAB, [ordered_set, named_table, public, - {keypos, #alias.key}, {heir, self(), failover}]); - _ -> - ?TAB + undefined -> + ets:new(?TAB, [ordered_set, named_table, public, + {keypos, #alias.key}, {heir, self(), failover}]); + _ -> + ?TAB end. ets_lookup(Key) -> ets:lookup(?TAB, Key). @@ -430,15 +430,15 @@ do_load(F) -> check_map(Map), lists:foreach( fun({Entry, DPs}) when is_list(Entry), is_list(DPs) -> - lists:foreach( - fun({DP, Alias}) when is_atom(DP), is_atom(Alias); - is_atom(DP), is_binary(Alias) -> - Key = to_key(Alias), - true = ets:insert_new(?TAB, #alias{key = Key, + lists:foreach( + fun({DP, Alias}) when is_atom(DP), is_atom(Alias); + is_atom(DP), is_binary(Alias) -> + Key = to_key(Alias), + true = ets:insert_new(?TAB, #alias{key = Key, alias = Alias, entry = Entry, dp = DP}) - end, DPs) + end, DPs) end, Map). check_map(Map) -> @@ -524,15 +524,15 @@ do_unload(F) -> Map = F(), lists:foreach( fun({Entry, DPs}) when is_list(Entry), is_list(DPs) -> - lists:foreach( - fun({DP, Alias}) when is_atom(Alias); + lists:foreach( + fun({DP, Alias}) when is_atom(Alias); is_binary(Alias) -> - Key = to_key(Alias), - ets:delete_object(?TAB, #alias{key = Key, + Key = to_key(Alias), + ets:delete_object(?TAB, #alias{key = Key, alias = Alias, entry = Entry, dp = DP}) - end, DPs) + end, DPs) end, Map). re_compile(R) -> @@ -564,11 +564,11 @@ regexp_prefix_(<<>>, Acc) -> alias_test_() -> {setup, fun() -> - exometer:start(), - create_entries(), - load_aliases(), - ets:tab2list(?TAB), - ok + exometer:start(), + create_entries(), + load_aliases(), + ets:tab2list(?TAB), + ok end, fun(_) -> application:stop(exometer) end, [?_test(t_resolve()), @@ -614,7 +614,7 @@ t_get_value() -> t_prefix_match() -> ?assertMatch( [{<>, [?Pfx,g,1 ],value}, - {<>, [?Pfx,g,10],value}], + {<>, [?Pfx,g,10],value}], prefix_match(<>)). t_prefix_match2() -> @@ -624,29 +624,29 @@ t_prefix_match2() -> t_prefix_foldl() -> ?assertMatch( [{<>, [?Pfx,g,10],value}, - {<>, [?Pfx,g,1 ],value}], + {<>, [?Pfx,g,1 ],value}], prefix_foldl(<>, - fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). + fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). t_regexp_foldl() -> ?assertMatch( [{<>,[?Pfx,g,5],value}, - {<>,[?Pfx,g,4],value}, - {<>,[?Pfx,g,3],value}], + {<>,[?Pfx,g,4],value}, + {<>,[?Pfx,g,3],value}], regexp_foldl(<<"^",?Pfx,"_g_[345]$">>, - fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). + fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). t_regexp_foldl2() -> ?assertMatch([], regexp_foldl(<<"^",?Pfx,"_g_[ab]$">>, - fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). + fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). t_regexp_foldr() -> ?assertMatch( [{<>,[?Pfx,g,3],value}, - {<>,[?Pfx,g,4],value}, - {<>,[?Pfx,g,5],value}], + {<>,[?Pfx,g,4],value}, + {<>,[?Pfx,g,5],value}], regexp_foldr(<<"^",?Pfx,"_g_[345]$">>, - fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). + fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). t_unload() -> ok = unload(fun test_aliases/0), @@ -672,7 +672,7 @@ load_aliases() -> test_aliases() -> [{[?Pfx,g,N], [{value, iolist_to_binary([?Pfx, "_g_", - integer_to_list(N)])}]} + integer_to_list(N)])}]} || N <- lists:seq(1,10)]. -endif. diff --git a/src/exometer_core_sup.erl b/src/exometer_core_sup.erl index 9b910a8..44268d1 100644 --- a/src/exometer_core_sup.erl +++ b/src/exometer_core_sup.erl @@ -35,10 +35,10 @@ start_link() -> init([]) -> Children0 = [ - ?CHILD(exometer_admin, worker), - ?CHILD(exometer_cache, worker), - ?CHILD(exometer_report, worker), - ?CHILD(exometer_folsom_monitor, worker), - ?CHILD(exometer_alias, worker) - ], + ?CHILD(exometer_admin, worker), + ?CHILD(exometer_cache, worker), + ?CHILD(exometer_report, worker), + ?CHILD(exometer_folsom_monitor, worker), + ?CHILD(exometer_alias, worker) + ], {ok, {{one_for_one, 5, 10}, Children0}}. diff --git a/src/exometer_cpu.erl b/src/exometer_cpu.erl index 0c141b3..2f9001b 100644 --- a/src/exometer_cpu.erl +++ b/src/exometer_cpu.erl @@ -49,7 +49,7 @@ probe_init(_, _, Opts) -> probe_terminate(_) -> ok. probe_get_value(DPs, #st{data = Data0, - datapoints = DPs0} = S) -> + datapoints = DPs0} = S) -> Data1 = if Data0 == undefined -> sample(DPs0); true -> Data0 end, diff --git a/src/exometer_duration.erl b/src/exometer_duration.erl index e4d57ff..097995a 100644 --- a/src/exometer_duration.erl +++ b/src/exometer_duration.erl @@ -13,18 +13,18 @@ %% exometer_entry callbacks -export([new/3, - delete/3, - get_value/3, - get_value/4, - get_datapoints/3, - setopts/3, - update/4, - reset/3, - sample/3]). + delete/3, + get_value/3, + get_value/4, + get_datapoints/3, + setopts/3, + update/4, + reset/3, + sample/3]). %% exometer_probe callbacks -export([behaviour/0, - probe_init/3, + probe_init/3, probe_terminate/1, probe_get_value/2, probe_update/2, @@ -38,10 +38,10 @@ -include("exometer.hrl"). -import(netlink_stat, [get_value/1]). -record(st, {name, - t_start, - count = 0, - last = 0, - histogram, + t_start, + count = 0, + last = 0, + histogram, opts = []}). @@ -98,11 +98,11 @@ probe_get_datapoints(#st{histogram = H}) -> probe_get_value(DataPoints, #st{histogram = H} = St) -> case DataPoints -- ?DATAPOINTS of - [] -> - {ok, fill_datapoints(DataPoints, [], St)}; - HDPs -> - {ok, HVals} = exometer_histogram:probe_get_value(HDPs, H), - {ok, fill_datapoints(DataPoints, HVals, St)} + [] -> + {ok, fill_datapoints(DataPoints, [], St)}; + HDPs -> + {ok, HVals} = exometer_histogram:probe_get_value(HDPs, H), + {ok, fill_datapoints(DataPoints, HVals, St)} end. probe_setopts(_Entry, _Opts, _St) -> @@ -112,12 +112,12 @@ probe_update({timer_start, T}, St) -> {ok, St#st{t_start = T}}; probe_update({timer_end, T}, #st{histogram = H, count = C} = St) -> try - Duration = timer:now_diff(T, St#st.t_start), - {ok, H1} = exometer_histogram:probe_update(Duration, H), - {ok, St#st{histogram = H1, count = C+1, last = Duration}} + Duration = timer:now_diff(T, St#st.t_start), + {ok, H1} = exometer_histogram:probe_update(Duration, H), + {ok, St#st{histogram = H1, count = C+1, last = Duration}} catch - error:_ -> - {ok, St} + error:_ -> + {ok, St} end. probe_reset(#st{histogram = H} = St) -> diff --git a/src/exometer_folsom.erl b/src/exometer_folsom.erl index b1f1db4..5bffc26 100644 --- a/src/exometer_folsom.erl +++ b/src/exometer_folsom.erl @@ -12,7 +12,7 @@ -behaviour(exometer_entry). -export([behaviour/0, - new/3, + new/3, delete/3, get_datapoints/3, get_value/4, @@ -47,8 +47,8 @@ new(Name, duration, Opts) -> {folsom_metrics:new_duration(Name), opt_ref(Opts)}; new(Name, history, Opts) -> case lists:keyfind(size, 1, Opts) of - {_, Sz} -> {folsom_metrics:new_history(Name, Sz), opt_ref(Opts)}; - false -> {folsom_metrics:new_history(Name), opt_ref(Opts)} + {_, Sz} -> {folsom_metrics:new_history(Name, Sz), opt_ref(Opts)}; + false -> {folsom_metrics:new_history(Name), opt_ref(Opts)} end. @@ -81,30 +81,30 @@ reset(_, _, _) -> get_value(Name, history, _Ref, DataPoints0) -> try DataPoints = datapoints(history, DataPoints0), - lists:foldr( - fun(events, Acc) -> - [{events, just_events( - folsom_metrics_history:get_events(Name))} - | Acc]; - (values, Acc) -> - [{values, folsom_metrics_history:get_events(Name)} - | Acc]; - (timed_events, Acc) -> - [{timed_events, - timed_events( - folsom_metrics_history:get_events(Name))} - | Acc]; - (Sz, Acc) when is_integer(Sz), Sz > 0 -> - [{Sz, just_events( - folsom_metrics_history:get_events(Name, Sz))} - | Acc]; - (info, Acc) -> - [{info, folsom_metrics_history:get_value(Name)} - | Acc]; - (_, Acc) -> Acc - end, [], DataPoints) + lists:foldr( + fun(events, Acc) -> + [{events, just_events( + folsom_metrics_history:get_events(Name))} + | Acc]; + (values, Acc) -> + [{values, folsom_metrics_history:get_events(Name)} + | Acc]; + (timed_events, Acc) -> + [{timed_events, + timed_events( + folsom_metrics_history:get_events(Name))} + | Acc]; + (Sz, Acc) when is_integer(Sz), Sz > 0 -> + [{Sz, just_events( + folsom_metrics_history:get_events(Name, Sz))} + | Acc]; + (info, Acc) -> + [{info, folsom_metrics_history:get_value(Name)} + | Acc]; + (_, Acc) -> Acc + end, [], DataPoints) catch - error:_ -> unavailable + error:_ -> unavailable end; get_value(Name, Type, Ref, DataPoints) -> Trunc = get_trunc_opt(Ref), diff --git a/src/exometer_folsom_monitor.erl b/src/exometer_folsom_monitor.erl index b239234..a166552 100644 --- a/src/exometer_folsom_monitor.erl +++ b/src/exometer_folsom_monitor.erl @@ -24,12 +24,12 @@ -behaviour(gen_server). -export([start_link/0, - init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2, - code_change/3]). + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). -export([monitor/2]). -export([hook/1]). @@ -78,9 +78,9 @@ start_link() -> %% @private init(_) -> Mon = lists:foldl( - fun({Mf, Mc}, D) -> - orddict:append(Mf, Mc, D) - end, orddict:new(), find_env()), + fun({Mf, Mc}, D) -> + orddict:append(Mf, Mc, D) + end, orddict:new(), find_env()), init_monitor(Mon), {ok, #st{mon = Mon}}. @@ -89,7 +89,7 @@ find_env() -> E2 = exometer_util:get_env(folsom_monitor, []), lists:flatmap( fun({_,_} = M) -> [M]; - (L) when is_list(L) -> L + (L) when is_list(L) -> L end, E1 ++ E2). %% @private @@ -120,20 +120,20 @@ init_monitor([_|_]) -> do_init_monitor() -> case is_transformed() of - true -> - lager:debug("already transformed...~n", []), - ok; - false -> - lager:debug("transforming folsom_metrics...~n", []), - parse_trans_mod:transform_module(folsom_metrics, fun pt/2, []) + true -> + lager:debug("already transformed...~n", []), + ok; + false -> + lager:debug("transforming folsom_metrics...~n", []), + parse_trans_mod:transform_module(folsom_metrics, fun pt/2, []) end. pt(Forms, _) -> Funcs = funcs(), NewForms = parse_trans:plain_transform( - fun(F) -> - plain_pt(F, Funcs) - end, Forms), + fun(F) -> + plain_pt(F, Funcs) + end, Forms), mark_transformed(NewForms). is_transformed() -> @@ -147,10 +147,10 @@ mark_transformed([H|T]) -> plain_pt({function,L,F,A,Cs}, Funcs) -> case lists:keyfind({F,A}, 1, Funcs) of - {_, Type} -> - {function,L,F,A,insert_hook(Type, Cs)}; - false -> - continue + {_, Type} -> + {function,L,F,A,insert_hook(Type, Cs)}; + false -> + continue end; plain_pt(_, _) -> continue. @@ -169,10 +169,10 @@ funcs() -> insert_hook(Type, Cs) -> lists:map( fun({clause,L0,Args,Gs,Body}) -> - L = element(2,hd(Body)), - {clause,L0,Args,Gs, - [{call,L,{remote,L,{atom,L,?MODULE},{atom,L,hook}}, - [cons([{atom,L,Type}|Args], L)]}|Body]} + L = element(2,hd(Body)), + {clause,L0,Args,Gs, + [{call,L,{remote,L,{atom,L,?MODULE},{atom,L,hook}}, + [cons([{atom,L,Type}|Args], L)]}|Body]} end, Cs). cons([H|T], L) -> {cons,L,H,cons(T,L)}; @@ -181,32 +181,32 @@ cons([] , L) -> {nil,L}. check_stack(Mon, Stack, Args) -> orddict:fold( fun('_', CBs, Acc) -> - _ = [maybe_create(CB, Args) || CB <- CBs], - Acc; - (Mod, CBs, Acc) -> - case lists:keymember(Mod, 1, Stack) of - true -> - _ = [maybe_create(CB, Args) || CB <- CBs]; - false -> - ignore - end, - Acc + _ = [maybe_create(CB, Args) || CB <- CBs], + Acc; + (Mod, CBs, Acc) -> + case lists:keymember(Mod, 1, Stack) of + true -> + _ = [maybe_create(CB, Args) || CB <- CBs]; + false -> + ignore + end, + Acc end, ok, Mon). maybe_create(CB, [FolsomType, Name | Args]) -> try CB:copy_folsom(Name, FolsomType, Args) of - {ExoName, ExoType, ExoArgs} -> - exometer:new(ExoName, ExoType, ExoArgs); - L when is_list(L) -> - lists:foreach( - fun({ExoName, ExoType, ExoArgs}) -> - exometer:new(ExoName, ExoType, ExoArgs) - end, L); - false -> - ignore + {ExoName, ExoType, ExoArgs} -> + exometer:new(ExoName, ExoType, ExoArgs); + L when is_list(L) -> + lists:foreach( + fun({ExoName, ExoType, ExoArgs}) -> + exometer:new(ExoName, ExoType, ExoArgs) + end, L); + false -> + ignore catch - Cat:Msg -> - lager:error("~p:copy_folsom(~p,~p,~p): ~p:~p~n", - [CB, Name, FolsomType, Args, Cat, Msg]), - ignore + Cat:Msg -> + lager:error("~p:copy_folsom(~p,~p,~p): ~p:~p~n", + [CB, Name, FolsomType, Args, Cat, Msg]), + ignore end. diff --git a/src/exometer_function.erl b/src/exometer_function.erl index b1fe197..07d9d24 100644 --- a/src/exometer_function.erl +++ b/src/exometer_function.erl @@ -13,7 +13,7 @@ -behaviour(exometer_entry). -export([behaviour/0, - new/3, + new/3, update/4, reset/3, get_value/4, @@ -45,11 +45,11 @@ arg_spec(), res_type(), datapoints()}. -type fun_spec() :: simple_fun() | extended_fun(). -type fun_rep() :: {mod_name(), fun_name()} - | {mod_name(), fun_name(), each | once, - arg_spec(), res_type(), datapoints()} - | {mod_name(), fun_name(), each | once, - arg_spec(), match, any()} - | {eval, [expr()], datapoints()}. + | {mod_name(), fun_name(), each | once, + arg_spec(), res_type(), datapoints()} + | {mod_name(), fun_name(), each | once, + arg_spec(), match, any()} + | {eval, [expr()], datapoints()}. -spec behaviour() -> exometer:behaviour(). behaviour() -> @@ -257,7 +257,7 @@ actual_datapoints(DPs0, DPs, _) -> get_datapoints(_Name, _Type, {_, _, once, _, match, Pat}) -> pattern_datapoints(Pat); get_datapoints(_Name, _Type, T) when is_tuple(T), is_list( - element(size(T),T)) -> + element(size(T),T)) -> element(size(T), T); get_datapoints(_Name, _Type, _Ref) -> [value]. @@ -580,20 +580,20 @@ e({element,E,T}, Bs) -> end; e({histogram, Vs}, Bs) -> case e(Vs, Bs) of - L when is_list(L) -> - exometer_util:histogram(L); - _ -> error(badarg) + L when is_list(L) -> + exometer_util:histogram(L); + _ -> error(badarg) end; e({histogram, Vs, DPs}, Bs) -> case e(Vs, Bs) of - L when is_list(L) -> - DataPoints = case e(DPs, Bs) of - default -> default; - D when is_list(D) -> D; - _ -> error(badarg) - end, - exometer_util:histogram(L, DataPoints); - _ -> error(badarg) + L when is_list(L) -> + DataPoints = case e(DPs, Bs) of + default -> default; + D when is_list(D) -> D; + _ -> error(badarg) + end, + exometer_util:histogram(L, DataPoints); + _ -> error(badarg) end; e({fold,Vx,Va,Es,Ea,El}, Bs) when is_atom(Vx), is_atom(Va) -> case e(El, Bs) of diff --git a/src/exometer_histogram.erl b/src/exometer_histogram.erl index ab7efe8..a6116b6 100644 --- a/src/exometer_histogram.erl +++ b/src/exometer_histogram.erl @@ -60,25 +60,25 @@ %% exometer_probe callbacks -export([behaviour/0, - probe_init/3, - probe_terminate/1, - probe_setopts/3, - probe_update/2, - probe_get_value/2, - probe_get_datapoints/1, - probe_reset/1, - probe_code_change/3, - probe_sample/1, - probe_handle_msg/2]). + probe_init/3, + probe_terminate/1, + probe_setopts/3, + probe_update/2, + probe_get_value/2, + probe_get_datapoints/1, + probe_reset/1, + probe_code_change/3, + probe_sample/1, + probe_handle_msg/2]). -compile(inline). -compile(inline_list_funcs). -export([datapoints/0]). -export([average_sample/3, - average_transform/2]). + average_transform/2]). -export([test_run/1, test_run/2, - test_series/0]). + test_series/0]). %% -compile({parse_transform, exometer_igor}). %% -compile({igor, [{files, ["src/exometer_util.erl" @@ -95,7 +95,7 @@ time_span = 60000, %% msec truncate = true, histogram_module = exometer_slot_slide, - heap, + heap, opts = []}). %% for auto-conversion @@ -109,30 +109,30 @@ behaviour() -> probe. probe_init(Name, _Type, Options) -> - {ok, init_state(Name, Options)}. + {ok, init_state(Name, Options)}. init_state(Name, Options) -> St = process_opts(#st{name = Name}, - [{histogram_module, exometer_slot_slide}, - {time_span, 60000}, - {slot_period, 10}] ++ Options), + [{histogram_module, exometer_slot_slide}, + {time_span, 60000}, + {slot_period, 10}] ++ Options), Slide = (St#st.histogram_module):new(St#st.time_span, - St#st.slot_period, - fun average_sample/3, - fun average_transform/2, - Options), + St#st.slot_period, + fun average_sample/3, + fun average_transform/2, + Options), Heap = if St#st.histogram_module == exometer_slot_slide -> - case lists:keyfind(keep_high, 1, Options) of - false -> - undefined; - {_, N} when is_integer(N), N > 0 -> - T = exometer_shallowtree:new(N), - {T, T}; - {_, 0} -> - undefined - end; - true -> undefined - end, + case lists:keyfind(keep_high, 1, Options) of + false -> + undefined; + {_, N} when is_integer(N), N > 0 -> + T = exometer_shallowtree:new(N), + {T, T}; + {_, 0} -> + undefined + end; + true -> undefined + end, St#st{slide = Slide, heap = Heap}. @@ -164,26 +164,26 @@ get_value_int(St, DataPoints) -> get_value_int_(#st{truncate = Trunc, histogram_module = Module, - time_span = TimeSpan, - heap = Heap} = St, DataPoints) -> + time_span = TimeSpan, + heap = Heap} = St, DataPoints) -> %% We need element count and sum of all elements to get mean value. Tot0 = case Trunc of true -> 0; round -> 0; false -> 0.0 end, TS = exometer_util:timestamp(), {Length, FullLength, Total, Min0, Max, Lst0, Xtra} = Module:foldl( - TS, + TS, fun ({_TS1, {Val, Cnt, NMin, NMax, X}}, {Length, FullLen, Total, OMin, OMax, List, Xs}) -> - {Length + 1, FullLen + Cnt, Total + Val, - min(OMin, NMin), max(OMax, NMax), - [Val|List], [X|Xs]}; + {Length + 1, FullLen + Cnt, Total + Val, + min(OMin, NMin), max(OMax, NMax), + [Val|List], [X|Xs]}; ({_TS1, Val}, {Length, _, Total, Min, Max, List, Xs}) -> - L1 = Length+1, - {L1, L1, Total + Val, min(Val, Min), max(Val, Max), - [Val|List], Xs} - end, + L1 = Length+1, + {L1, L1, Total + Val, min(Val, Min), max(Val, Max), + [Val|List], Xs} + end, {0, 0, Tot0, infinity, 0, [], []}, St#st.slide), Min = if Min0 == infinity -> 0; true -> Min0 end, Mean = case Length of @@ -205,23 +205,23 @@ get_value_int_(#st{truncate = Trunc, get_from_heap({New,Old}, TS, TSpan, N, DPs) when N > 0 -> Sz = exometer_shallowtree:size(New) - + exometer_shallowtree:size(Old), + + exometer_shallowtree:size(Old), if Sz > 0 -> - MinPerc = 100 - ((Sz*100) div N), - MinPerc10 = MinPerc * 10, - GetDPs = lists:foldl( - fun(D, Acc) when is_integer(D), - D < 100, D >= MinPerc -> - [{D, p(D, N)}|Acc]; - (D, Acc) when is_integer(D), - D > 100, D >= MinPerc10 -> - [{D, p(D, N)}|Acc]; - (_, Acc) -> - Acc - end, [], DPs), - pick_heap_vals(GetDPs, New, Old, TS, TSpan); + MinPerc = 100 - ((Sz*100) div N), + MinPerc10 = MinPerc * 10, + GetDPs = lists:foldl( + fun(D, Acc) when is_integer(D), + D < 100, D >= MinPerc -> + [{D, p(D, N)}|Acc]; + (D, Acc) when is_integer(D), + D > 100, D >= MinPerc10 -> + [{D, p(D, N)}|Acc]; + (_, Acc) -> + Acc + end, [], DPs), + pick_heap_vals(GetDPs, New, Old, TS, TSpan); true -> - [] + [] end; get_from_heap(_, _, _, _, _) -> []. @@ -232,13 +232,13 @@ pick_heap_vals(DPs, New, Old, TS, TSpan) -> TS0 = TS - TSpan, NewVals = exometer_shallowtree:filter(fun(V,_) -> {true,V} end, New), OldVals = exometer_shallowtree:filter( - fun(V,T) -> - if T >= TS0 -> - {true, V}; - true -> - false - end - end, Old), + fun(V,T) -> + if T >= TS0 -> + {true, V}; + true -> + false + end + end, Old), Vals = revsort(OldVals ++ NewVals), exometer_util:pick_items(Vals, DPs). @@ -296,15 +296,15 @@ probe_update(Value, ?OLDSTATE = St) -> probe_update(Value, convert(St)); probe_update(Value, St) -> if is_number(Value) -> - {ok, update_int(exometer_util:timestamp(), Value, St)}; + {ok, update_int(exometer_util:timestamp(), Value, St)}; true -> - %% ignore - {ok, St} + %% ignore + {ok, St} end. update_int(Timestamp, Value, #st{slide = Slide, - histogram_module = Module, - heap = Heap} = St) -> + histogram_module = Module, + heap = Heap} = St) -> {Wrapped, Slide1} = Module:add_element(Timestamp, Value, Slide, true), St#st{slide = Slide1, heap = into_heap(Wrapped, Value, Timestamp, Heap)}. @@ -320,7 +320,7 @@ into_heap(true, Val, TS, {New,_}) -> probe_reset(?OLDSTATE = St) -> probe_reset(convert(St)); probe_reset(#st{slide = Slide, - histogram_module = Module} = St) -> + histogram_module = Module} = St) -> {ok, St#st{slide = Module:reset(Slide)}}. probe_sample(_St) -> @@ -335,10 +335,10 @@ probe_code_change(_, S, _) -> {ok, S}. convert({st, Name, Slide, Slot_period, Time_span, - Truncate, Histogram_module, Opts}) -> + Truncate, Histogram_module, Opts}) -> #st{name = Name, slide = Slide, slot_period = Slot_period, - time_span = Time_span, truncate = Truncate, - histogram_module = Histogram_module, opts = Opts}. + time_span = Time_span, truncate = Truncate, + histogram_module = Histogram_module, opts = Opts}. process_opts(St, Options) -> exometer_proc:process_options(Options), @@ -349,13 +349,13 @@ process_opts(St, Options) -> ( {slot_period, Val}, St1) -> St1#st {slot_period = Val}; ( {histogram_module, Val}, St1) -> St1#st {histogram_module = Val}; ( {truncate, Val}, St1) when is_boolean(Val); Val == round -> - St1#st{truncate = Val}; + St1#st{truncate = Val}; %% Unknown option, pass on to State options list, replacing %% any earlier versions of the same option. ({Opt, Val}, St1) -> - St1#st{ opts = [ {Opt, Val} - | lists:keydelete(Opt, 1, St1#st.opts) ] } - end, St, Options). + St1#st{ opts = [ {Opt, Val} + | lists:keydelete(Opt, 1, St1#st.opts) ] } + end, St, Options). -record(sample, {count, total, min, max, extra = []}). %% Simple sample processor that maintains an average @@ -436,13 +436,13 @@ test_run(Module, Interval) -> test_run(Module, Int, Series) -> St = test_new(test_opts(Module)), {T1, St1} = tc(fun() -> - test_update( - Series, Int, - exometer_util:timestamp(), St) - end), + test_update( + Series, Int, + exometer_util:timestamp(), St) + end), {T2, Result} = tc(fun() -> - get_value_int(St1, default) - end), + get_value_int(St1, default) + end), erlang:garbage_collect(), erlang:yield(), {T1, T2, Result}. @@ -494,14 +494,14 @@ tc(F) -> %% @end test_series() -> S = lists:flatten( - [dupl(200,3), - dupl(100,4), - dupl(200,5), - dupl(100,6), - dupl(200,7), - dupl(100,8), - dupl(80,9), - dupl(15,50), 80,81,82,83,100]), + [dupl(200,3), + dupl(100,4), + dupl(200,5), + dupl(100,6), + dupl(200,7), + dupl(100,8), + dupl(80,9), + dupl(15,50), 80,81,82,83,100]), shuffle(S ++ S ++ S ++ S ++ S ++ S ++ S ++ S ++ S). dupl(N,V) -> diff --git a/src/exometer_igor.erl b/src/exometer_igor.erl index f4668b4..3fd4100 100644 --- a/src/exometer_igor.erl +++ b/src/exometer_igor.erl @@ -8,8 +8,8 @@ parse_transform(Forms, Opts) -> Includes = [I || {i,I} <- Opts], NewForms = igor:parse_transform( - Forms, [{igor, IgorOpts ++ [{includes, Includes}, - {preprocess, true}]}|Opts]), + Forms, [{igor, IgorOpts ++ [{includes, Includes}, + {preprocess, true}]}|Opts]), fix_for_r16b03(NewForms). %% erl_syntax:revert/1 is horribly broken in R16B03. This transform diff --git a/src/exometer_info.erl b/src/exometer_info.erl index c5ef43c..f6d2235 100644 --- a/src/exometer_info.erl +++ b/src/exometer_info.erl @@ -20,10 +20,10 @@ -module(exometer_info). -export([status/1, - pp/1, - pp_lookup/1, - pp_find/1, - pp_select/1]). + pp/1, + pp_lookup/1, + pp_find/1, + pp_select/1]). -include("exometer.hrl"). -include_lib("parse_trans/include/exprecs.hrl"). @@ -74,10 +74,10 @@ pp(X) -> %% @end pp_lookup(Name) -> case exometer:info(Name, entry) of - undefined -> - undefined; - Entry -> - pp(Entry) + undefined -> + undefined; + Entry -> + pp(Entry) end. -spec pp_find(list()) -> [pp()]. diff --git a/src/exometer_probe.erl b/src/exometer_probe.erl index eee16da..bb19268 100644 --- a/src/exometer_probe.erl +++ b/src/exometer_probe.erl @@ -381,7 +381,7 @@ -behaviour(exometer_entry). -% exometer_entry callb + % exometer_entry callb -export( [ behaviour/0, @@ -413,13 +413,13 @@ -type mod_state() :: any(). -type data_points() :: [atom()]. -type probe_reply() :: ok - | {ok, mod_state()} - | {ok, any(), mod_state()} - | {noreply, mod_state()} - | {error, any()}. + | {ok, mod_state()} + | {ok, any(), mod_state()} + | {noreply, mod_state()} + | {error, any()}. -type probe_noreply() :: ok - | {ok, mod_state()} - | {error, any()}. + | {ok, mod_state()} + | {error, any()}. -callback behaviour() -> exometer:behaviour(). -callback probe_init(name(), type(), options()) -> probe_noreply(). @@ -438,9 +438,9 @@ new(Name, Type, [{arg, Module}|Opts]) -> { ok, exometer_proc:spawn_process( - Name, fun() -> - init(Name, Type, Module, Opts) - end) + Name, fun() -> + init(Name, Type, Module, Opts) + end) }; @@ -487,14 +487,14 @@ init(Name, Type, Mod, Opts) -> %% Create a new state for the module case {Mod:probe_init(Name, Type, St#st.opts), - St#st.sample_interval} of + St#st.sample_interval} of { ok, infinity} -> %% No sample timer to start. Return with undefined mod state - loop(St#st{ mod_state = undefined }); + loop(St#st{ mod_state = undefined }); {{ok, ModSt}, infinity} -> %% No sample timer to start. Return with the mod state returned by probe_init. - loop(St#st{ mod_state = ModSt }); + loop(St#st{ mod_state = ModSt }); {ok, _} -> %% Fire up the timer, with undefined mod @@ -505,7 +505,7 @@ init(Name, Type, Mod, Opts) -> loop(sample(St#st{ mod_state = ModSt })); {{error, Reason}, _} -> - %% FIXME: Proper shutdown. + %% FIXME: Proper shutdown. {error, Reason} end. @@ -518,21 +518,21 @@ loop(St) -> handle_msg(Msg, St) -> Module = St#st.module, case Msg of - {system, From, Req} -> - exometer_proc:handle_system_msg( - Req, From, St, fun(St1) -> loop(St1) end); + {system, From, Req} -> + exometer_proc:handle_system_msg( + Req, From, St, fun(St1) -> loop(St1) end); {exometer_proc, {From, Ref}, {get_value, default} } -> {ok, DataPoints} = Module:probe_get_datapoints(St#st.mod_state), {Reply, NSt} = - process_probe_reply(St, Module:probe_get_value(DataPoints, - St#st.mod_state)), + process_probe_reply(St, Module:probe_get_value(DataPoints, + St#st.mod_state)), From ! {Ref, Reply }, NSt; {exometer_proc, {From, Ref}, {get_value, DataPoints} } -> {Reply, NSt} = - process_probe_reply(St, Module:probe_get_value(DataPoints, - St#st.mod_state)), + process_probe_reply(St, Module:probe_get_value(DataPoints, + St#st.mod_state)), From ! {Ref, Reply }, NSt; @@ -556,14 +556,14 @@ handle_msg(Msg, St) -> {NSt, Opts1} = process_opts(Options, St), {Reply, NSt1} = %% Call module setopts for remainder of opts - process_probe_reply( - NSt, Module:probe_setopts(Entry, Opts1, NSt#st.mod_state)), + process_probe_reply( + NSt, Module:probe_setopts(Entry, Opts1, NSt#st.mod_state)), From ! {Ref, Reply }, %% Return state with options and any non-duplicate original opts. NSt1#st { opts = Opts1 ++ - [{K,V} || {K,V} <- St#st.opts, not lists:keymember(K,1,Opts1) ] + [{K,V} || {K,V} <- St#st.opts, not lists:keymember(K,1,Opts1) ] }; {timeout, _TRef, {exometer_proc, sample_timer}} -> diff --git a/src/exometer_proc.erl b/src/exometer_proc.erl index 7e75107..a2d14ed 100644 --- a/src/exometer_proc.erl +++ b/src/exometer_proc.erl @@ -74,7 +74,7 @@ spawn_process(Name, F) when is_function(F,0) -> Parent = self(), proc_lib:spawn(fun() -> exometer_admin:monitor(Name, self()), - init(Name, Mod, F, Parent) + init(Name, Mod, F, Parent) end). init(Name, Mod, StartF, ParentPid) -> @@ -201,8 +201,8 @@ format_status(Opt, StatusData) -> pid_to_list(Name); is_atom(Name) -> Name; - true -> - lists:flatten(io_lib:fwrite("~w", [Name])) + true -> + lists:flatten(io_lib:fwrite("~w", [Name])) end, Header = lists:concat(["Status for exometer_proc ", NameTag]), Log = sys:get_debug(log, Debug, []), diff --git a/src/exometer_report.erl b/src/exometer_report.erl index 619404b..17927e6 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -189,8 +189,8 @@ -type error() :: {error, any()}. -type metric() :: exometer:name() - | {find, exometer:name()} - | {select, ets:match_spec()}. + | {find, exometer:name()} + | {select, ets:match_spec()}. -type datapoint() :: exometer:datapoint(). -type datapoints() :: datapoint() | [datapoint()]. -type options() :: [{atom(), any()}]. @@ -200,7 +200,7 @@ -type time_ms() :: pos_integer(). -type delay() :: time_ms(). -type named_interval() :: {atom(), time_ms()} - | {atom(), time_ms(), delay()}. + | {atom(), time_ms(), delay()}. -type callback_result() :: {ok, mod_state()} | any(). -type extra() :: any(). -type retry() :: boolean(). @@ -240,7 +240,7 @@ any(). -callback exometer_setopts(exometer:entry(), options(), - exometer:status(), mod_state()) -> + exometer:status(), mod_state()) -> callback_result(). -callback exometer_newentry(exometer:entry(), mod_state()) -> @@ -267,11 +267,11 @@ ). -record(interval, { - name :: atom(), - time = 0 :: non_neg_integer(), - delay = 0 :: non_neg_integer(), - t_ref :: reference() | undefined - }). + name :: atom(), + time = 0 :: non_neg_integer(), + delay = 0 :: non_neg_integer(), + t_ref :: reference() | undefined + }). -record(reporter, { name :: atom() | '_', @@ -279,9 +279,9 @@ mref :: reference() | '_', module :: module() | '_', opts = [] :: [{atom(), any()}] | '_', - intervals = [] :: [#interval{}] | '_', + intervals = [] :: [#interval{}] | '_', restart = #restart{} :: #restart{} | '_', - status = enabled :: enabled | disabled | '_' + status = enabled :: enabled | disabled | '_' }). -record(st, { @@ -302,13 +302,13 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). -spec subscribe(reporter_name(), metric(), datapoints(), interval()) -> - ok | not_found | unknown_reporter | error. + ok | not_found | unknown_reporter | error. %% @equiv subscribe(Reporter, Metric, DataPoint, Interval, [], false) subscribe(Reporter, Metric, DataPoint, Interval) -> subscribe(Reporter, Metric, DataPoint, Interval, []). -spec subscribe(reporter_name(), metric(), datapoints(), interval(), extra()) -> - ok | not_found | unknown_reporter | error. + ok | not_found | unknown_reporter | error. %% @equiv subscribe(Reporter, Metric, DataPoint, Interval, Extra, false) subscribe(Reporter, Metric, DataPoint, Interval, Extra) -> call({subscribe, #key{reporter = Reporter, @@ -318,8 +318,8 @@ subscribe(Reporter, Metric, DataPoint, Interval, Extra) -> extra = Extra}, Interval}). -spec subscribe(reporter_name(), metric(), datapoints(), interval(), - extra(), retry()) -> - ok | not_found | unknown_reporter | error. + extra(), retry()) -> + ok | not_found | unknown_reporter | error. %% @doc Add a subscription to an existing reporter. %% %% The reporter must first be started using {@link add_reporter/2}, or through @@ -342,19 +342,19 @@ subscribe(Reporter, Metric, DataPoint, Interval, Extra) -> subscribe(Reporter, Metric, DataPoint, Interval, Extra, Retry) when is_boolean(Retry) -> call({subscribe, #key{reporter = Reporter, - metric = Metric, - datapoint = DataPoint, - retry_failed_metrics = Retry, - extra = Extra}, Interval}). + metric = Metric, + datapoint = DataPoint, + retry_failed_metrics = Retry, + extra = Extra}, Interval}). -spec unsubscribe(module(), metric(), datapoint()) -> - ok | not_found. + ok | not_found. %% @equiv unsubscribe(Reporter, Metric, DataPoint, []) unsubscribe(Reporter, Metric, DataPoint) -> unsubscribe(Reporter, Metric, DataPoint, []). -spec unsubscribe(module(), metric(), datapoint() | [datapoint()], extra()) -> - ok | not_found. + ok | not_found. %% @doc Removes a subscription. %% %% Note that the subscription is identified by the combination @@ -374,18 +374,18 @@ unsubscribe_all(Reporter, Metric) -> call({unsubscribe_all, Reporter, Metric}). -spec list_metrics() -> {ok, [{ exometer:name(), - [datapoint()], - [{reporter_name(), datapoint()}], - exometer:status() }]} | {error, any()}. + [datapoint()], + [{reporter_name(), datapoint()}], + exometer:status() }]} | {error, any()}. %% @equiv list_metrics([]) list_metrics() -> list_metrics([]). -spec list_metrics(Path :: metric()) -> - {ok, [{ exometer:name(), - [datapoint()], - [{reporter_name(), datapoint()}], - exometer:status() }]} | {error, any()}. + {ok, [{ exometer:name(), + [datapoint()], + [{reporter_name(), datapoint()}], + exometer:status() }]} | {error, any()}. %% @doc List all metrics matching `Path', together with subscription status. %% %% This function performs a metrics search using `exometer:find_entries/1', @@ -402,7 +402,7 @@ list_reporters() -> call(list_reporters). -spec list_subscriptions(reporter_name()) -> - [{metric(), datapoint(), interval(), extra()}]. + [{metric(), datapoint(), interval(), extra()}]. %% @doc List all subscriptions for `Reporter'. list_subscriptions(Reporter) -> call({list_subscriptions, Reporter}). @@ -442,7 +442,7 @@ remove_reporter(Reporter) -> call({remove_reporter, Reporter}). -spec set_interval(reporter_name(), atom(), - time_ms() | {time_ms(), delay()}) -> ok |error(). + time_ms() | {time_ms(), delay()}) -> ok |error(). %% @doc Specify a named interval. %% %% See {@link add_reporter/2} for a description of named intervals. @@ -454,12 +454,12 @@ remove_reporter(Reporter) -> %% all intervals to be restarted/resynched with corresponding relative delays. %% @end set_interval(Reporter, Name, Time) when is_atom(Name), - is_integer(Time), Time >= 0 -> + is_integer(Time), Time >= 0 -> call({set_interval, Reporter, Name, Time}); set_interval(Reporter, Name, {Time, Delay}) when is_atom(Name), - is_integer(Time), Time >= 0, - is_integer(Delay), - Delay >= 0 -> + is_integer(Time), Time >= 0, + is_integer(Delay), + Delay >= 0 -> call({set_interval, Reporter, Name, {Time, Delay}}). -spec delete_interval(reporter_name(), atom()) -> ok | error(). @@ -478,9 +478,9 @@ restart_intervals(Reporter) -> call({restart_intervals, Reporter}). -spec get_intervals(reporter_name()) -> - [{atom(), [{time, pos_integer()} - | {delay, pos_integer()} - | {timer_ref, reference()}]}]. + [{atom(), [{time, pos_integer()} + | {delay, pos_integer()} + | {timer_ref, reference()}]}]. %% @doc List the named intervals for `Reporter'. get_intervals(Reporter) -> call({get_intervals, Reporter}). @@ -518,9 +518,9 @@ disable_reporter(Reporter) -> disable_me(Mod, St) -> cast({disable, self()}), receive - {exometer_terminate, shutdown} -> - Mod:exometer_terminate(shutdown, St), - exit(shutdown) + {exometer_terminate, shutdown} -> + Mod:exometer_terminate(shutdown, St), + exit(shutdown) end. -spec call_reporter(reporter_name(), any()) -> any() | {error, any()}. @@ -531,10 +531,10 @@ disable_me(Mod, St) -> %% @end call_reporter(Reporter, Msg) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{pid = Pid}] when is_pid(Pid) -> + [#reporter{pid = Pid}] when is_pid(Pid) -> exometer_proc:call(Pid, Msg); - [#reporter{status = disabled}] -> - {error, disabled}; + [#reporter{status = disabled}] -> + {error, disabled}; [] -> {error, {no_such_reporter, Reporter}} end. @@ -547,10 +547,10 @@ call_reporter(Reporter, Msg) -> %% @end cast_reporter(Reporter, Msg) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{pid = Pid}] when is_pid(Pid) -> + [#reporter{pid = Pid}] when is_pid(Pid) -> exometer_proc:cast(Pid, Msg); - [#reporter{status = disabled}] -> - {error, disabled}; + [#reporter{status = disabled}] -> + {error, disabled}; [] -> {error, {no_such_reporter, Reporter}} end. @@ -602,7 +602,7 @@ new_entry(Entry) -> %% @end %%-------------------------------------------------------------------- init([]) -> - process_flag(trap_exit, true), + process_flag(trap_exit, true), {ok, #st{}}. start_reporters() -> @@ -616,45 +616,45 @@ do_start_reporters(S) -> %% Traverse list of reporter and launch reporter gen servers as dynamic %% supervisor children. case lists:keyfind(reporters, 1, Opts) of - {reporters, ReporterList} -> - ReporterRecs = make_reporter_recs(ReporterList), - assert_no_duplicates(ReporterRecs), - lists:foreach( - fun(#reporter{name = Reporter, - status = Status, - opts = ROpts, - intervals = Ints0} = R) -> - Restart = get_restart(ROpts), - {Pid, MRef, Ints} = - if Status =:= enabled -> - {P1,R1} = spawn_reporter(Reporter, ROpts), - I1 = start_interval_timers(R), - {P1,R1,I1}; - true -> {undefined, undefined, Ints0} - end, - ets:insert(?EXOMETER_REPORTERS, - R#reporter{pid = Pid, - mref = MRef, - intervals = Ints, - restart = Restart}) - end, ReporterRecs); - false -> - [] + {reporters, ReporterList} -> + ReporterRecs = make_reporter_recs(ReporterList), + assert_no_duplicates(ReporterRecs), + lists:foreach( + fun(#reporter{name = Reporter, + status = Status, + opts = ROpts, + intervals = Ints0} = R) -> + Restart = get_restart(ROpts), + {Pid, MRef, Ints} = + if Status =:= enabled -> + {P1,R1} = spawn_reporter(Reporter, ROpts), + I1 = start_interval_timers(R), + {P1,R1,I1}; + true -> {undefined, undefined, Ints0} + end, + ets:insert(?EXOMETER_REPORTERS, + R#reporter{pid = Pid, + mref = MRef, + intervals = Ints, + restart = Restart}) + end, ReporterRecs); + false -> + [] end, %% Dig out configured 'static' subscribers case lists:keyfind(subscribers, 1, Opts) of - {subscribers, Subscribers} -> - lists:foreach(fun init_subscriber/1, Subscribers); - false -> [] + {subscribers, Subscribers} -> + lists:foreach(fun init_subscriber/1, Subscribers); + false -> [] end, S#st{}. make_reporter_recs([{R, Opts}|T]) when is_atom(R), is_list(Opts) -> [#reporter{name = R, module = get_module(R, Opts), - status = proplists:get_value(status, Opts, enabled), + status = proplists:get_value(status, Opts, enabled), opts = Opts, - intervals = get_interval_opts(Opts)}|make_reporter_recs(T)]; + intervals = get_interval_opts(Opts)}|make_reporter_recs(T)]; make_reporter_recs([]) -> []. @@ -667,33 +667,33 @@ get_interval_opts(Opts) -> Is = proplists:get_value(intervals, Opts, []), lists:map( fun({Name, Time}) when is_atom(Name), - is_integer(Time), Time >= 0 -> - #interval{name = Name, time = Time}; - ({Name, Time, Delay}) when is_atom(Name), - is_integer(Time), Time >= 0, - is_integer(Delay), Delay >= 0 -> - #interval{name = Name, time = Time, delay = Delay}; - (Other) -> - error({invalid_interval, Other}) + is_integer(Time), Time >= 0 -> + #interval{name = Name, time = Time}; + ({Name, Time, Delay}) when is_atom(Name), + is_integer(Time), Time >= 0, + is_integer(Delay), Delay >= 0 -> + #interval{name = Name, time = Time, delay = Delay}; + (Other) -> + error({invalid_interval, Other}) end, Is ++ Is1). singelton_interval({N,T}=I) when is_atom(N), is_integer(T) -> I; singelton_interval({N,T,D}=I) when is_atom(N), - is_integer(T), - is_integer(D) -> I. + is_integer(T), + is_integer(D) -> I. start_interval_timers(#reporter{name = R, intervals = Ints}) -> lists:map(fun(I) -> start_interval_timer(I, R) end, Ints). start_interval_timer(#interval{name = Name, delay = Delay, - t_ref = Ref} = I, R) -> + t_ref = Ref} = I, R) -> cancel_timer(Ref), case Delay of - 0 -> - do_start_interval_timer(I, R); - D -> - TRef = erlang:send_after(D, self(), {start_interval, R, Name}), - I#interval{t_ref = TRef} + 0 -> + do_start_interval_timer(I, R); + D -> + TRef = erlang:send_after(D, self(), {start_interval, R, Name}), + I#interval{t_ref = TRef} end. do_start_interval_timer(#interval{name = Name, time = Time} = I, R) -> @@ -765,29 +765,29 @@ handle_call({subscribe, %% Verify that the given metric/data point actually exist. case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{status = Status}] -> - case is_valid_metric(Metric, DataPoint) of - true -> - if Status =:= enabled -> - Reporter ! {exometer_subscribe, Metric, - DataPoint, Interval, Extra}; - true -> ignore - end, + [#reporter{status = Status}] -> + case is_valid_metric(Metric, DataPoint) of + true -> + if Status =:= enabled -> + Reporter ! {exometer_subscribe, Metric, + DataPoint, Interval, Extra}; + true -> ignore + end, subscribe_(Reporter, Metric, DataPoint, - Interval, RetryFailedMetrics, - Extra, Status), + Interval, RetryFailedMetrics, + Extra, Status), {reply, ok, St}; %% Nope - Not found. - false -> - case RetryFailedMetrics of - true -> - subscribe_(Reporter, Metric, DataPoint, - Interval, RetryFailedMetrics, - Extra, Status), - {reply, ok, St}; - false -> - {reply, not_found, St} - end; + false -> + case RetryFailedMetrics of + true -> + subscribe_(Reporter, Metric, DataPoint, + Interval, RetryFailedMetrics, + Extra, Status), + {reply, ok, St}; + false -> + {reply, not_found, St} + end; error -> {reply, error, St} end; [] -> @@ -805,43 +805,43 @@ handle_call({unsubscribe, handle_call({unsubscribe_all, Reporter, Metric}, _, #st{}=St) -> Subs = ets:select(?EXOMETER_SUBS, - [{#subscriber{key = #key{reporter = Reporter, - metric = Metric, - _ = '_'}, - _ = '_'}, [], ['$_']}]), + [{#subscriber{key = #key{reporter = Reporter, + metric = Metric, + _ = '_'}, + _ = '_'}, [], ['$_']}]), lists:foreach(fun unsubscribe_/1, Subs), {reply, ok, St}; handle_call({list_metrics, Path}, _, St) -> if is_list(Path) -> - DP = lists:foldr(fun(Metric, Acc) -> - retrieve_metric(Metric, Acc) - end, [], exometer:find_entries(Path)), - {reply, {ok, DP}, St}; + DP = lists:foldr(fun(Metric, Acc) -> + retrieve_metric(Metric, Acc) + end, [], exometer:find_entries(Path)), + {reply, {ok, DP}, St}; true -> - {reply, {error, badarg}, St} + {reply, {error, badarg}, St} end; handle_call({list_subscriptions, Reporter}, _, #st{} = St) -> Subs1 = lists:foldl( fun (#subscriber{key=#key{reporter=Rep}}=Sub, Acc) when Reporter == Rep -> - #subscriber{ - key=#key{ - metric=Metric, - datapoint=Dp, - extra=Extra}, - interval=Interval} = Sub, - [{Metric, Dp, Interval, Extra} | Acc]; + #subscriber{ + key=#key{ + metric=Metric, + datapoint=Dp, + extra=Extra}, + interval=Interval} = Sub, + [{Metric, Dp, Interval, Extra} | Acc]; (_, Acc) -> - Acc - end, [], ets:select(?EXOMETER_SUBS, [{'_',[],['$_']}])), + Acc + end, [], ets:select(?EXOMETER_SUBS, [{'_',[],['$_']}])), {reply, Subs1, St}; handle_call(list_reporters, _, #st{} = St) -> Info = ets:select(?EXOMETER_REPORTERS, - [{#reporter{name = '$1', pid = '$2', _ = '_'}, - [], [{{'$1', '$2'}}]}]), + [{#reporter{name = '$1', pid = '$2', _ = '_'}, + [], [{{'$1', '$2'}}]}]), {reply, Info, St}; handle_call({add_reporter, Reporter, Opts}, _, #st{} = St) -> @@ -849,24 +849,24 @@ handle_call({add_reporter, Reporter, Opts}, _, #st{} = St) -> true -> {reply, {error, already_running}, St}; false -> - try - [R] = make_reporter_recs([{Reporter, Opts}]), - {Pid, MRef} = spawn_reporter(Reporter, Opts), - Ints = start_interval_timers(R), - R1 = R#reporter{intervals = Ints, - pid = Pid, - mref = MRef}, - ets:insert(?EXOMETER_REPORTERS, R1), - {reply, ok, St} - catch - error:Reason -> - {reply, {error, Reason}, St} - end + try + [R] = make_reporter_recs([{Reporter, Opts}]), + {Pid, MRef} = spawn_reporter(Reporter, Opts), + Ints = start_interval_timers(R), + R1 = R#reporter{intervals = Ints, + pid = Pid, + mref = MRef}, + ets:insert(?EXOMETER_REPORTERS, R1), + {reply, ok, St} + catch + error:Reason -> + {reply, {error, Reason}, St} + end end; handle_call({remove_reporter, Reporter}, _, St) -> case do_remove_reporter(Reporter) of - ok -> + ok -> {reply, ok, St}; E -> {reply, E, St} @@ -874,79 +874,79 @@ handle_call({remove_reporter, Reporter}, _, St) -> handle_call({change_reporter_status, Reporter, Status}, _, St) -> case change_reporter_status(Reporter, Status) of - ok -> - {reply, ok, St}; - E -> - {reply, E, St} + ok -> + {reply, ok, St}; + E -> + {reply, E, St} end; handle_call({set_interval, Reporter, Name, Int}, _, #st{}=St) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{intervals = Ints}] -> - try - I0 = case lists:keyfind(Name, #interval.name, Ints) of - false -> #interval{name = Name}; - Interval -> Interval - end, - I1 = case Int of - {Time, Delay} when is_integer(Time), Time >= 0, - is_integer(Delay), Delay >= 0 -> - I0#interval{time = Time, delay = Delay}; - Time when is_integer(Time), Time >= 0 -> - I0#interval{time = Time} - end, - ets:update_element(?EXOMETER_REPORTERS, Reporter, - [{#reporter.intervals, - lists:keystore( - Name, #interval.name, Ints, - start_interval_timer(I1, Reporter))}]), - {reply, ok, St} - catch - error:Reason -> - {reply, {error, Reason}, St} - end; - [] -> - {reply, {error, not_found}, St} + [#reporter{intervals = Ints}] -> + try + I0 = case lists:keyfind(Name, #interval.name, Ints) of + false -> #interval{name = Name}; + Interval -> Interval + end, + I1 = case Int of + {Time, Delay} when is_integer(Time), Time >= 0, + is_integer(Delay), Delay >= 0 -> + I0#interval{time = Time, delay = Delay}; + Time when is_integer(Time), Time >= 0 -> + I0#interval{time = Time} + end, + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, + lists:keystore( + Name, #interval.name, Ints, + start_interval_timer(I1, Reporter))}]), + {reply, ok, St} + catch + error:Reason -> + {reply, {error, Reason}, St} + end; + [] -> + {reply, {error, not_found}, St} end; handle_call({delete_interval, Reporter, Name}, _, #st{} = St) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{intervals = Ints}] -> - case lists:keyfind(Name, #interval.name, Ints) of - #interval{t_ref = TRef} -> - cancel_timer(TRef), - ets:update_element(?EXOMETER_REPORTERS, Reporter, - [{#reporter.intervals, - lists:keydelete( - Name, #interval.name, Ints)}]), - {reply, ok, St}; - false -> - {reply, {error, not_found}, St} - end; - [] -> - {reply, {error, not_found}, St} + [#reporter{intervals = Ints}] -> + case lists:keyfind(Name, #interval.name, Ints) of + #interval{t_ref = TRef} -> + cancel_timer(TRef), + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, + lists:keydelete( + Name, #interval.name, Ints)}]), + {reply, ok, St}; + false -> + {reply, {error, not_found}, St} + end; + [] -> + {reply, {error, not_found}, St} end; handle_call({restart_intervals, Reporter}, _, #st{} = St) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{} = R] -> - Ints = start_interval_timers(R), - ets:update_element(?EXOMETER_REPORTERS, Reporter, - [{#reporter.intervals, Ints}]), - {reply, ok, St}; - [] -> - {reply, {error, not_found}, St} + [#reporter{} = R] -> + Ints = start_interval_timers(R), + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, Ints}]), + {reply, ok, St}; + [] -> + {reply, {error, not_found}, St} end; handle_call({get_intervals, Reporter}, _, #st{} = St) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{intervals = Ints}] -> - Info = - [{Name, [{time, T}, - {delay, D}, - {timer_ref, TR}]} || #interval{name = Name, - time = T, - delay = D, - t_ref = TR} <- Ints], - {reply, Info, St}; - [] -> - {reply, {error, not_found}, St} + [#reporter{intervals = Ints}] -> + Info = + [{Name, [{time, T}, + {delay, D}, + {timer_ref, TR}]} || #interval{name = Name, + time = T, + delay = D, + t_ref = TR} <- Ints], + {reply, Info, St}; + [] -> + {reply, {error, not_found}, St} end; handle_call({setopts, Metric, Options, Status}, _, #st{}=St) -> [erlang:send(Pid, {exometer_setopts, Metric, Options, Status}) @@ -983,9 +983,9 @@ handle_cast({remove_reporter, Reporter, Reason}, St) -> {noreply, St}; handle_cast({disable, Pid}, #st{} = St) -> case reporter_by_pid(Pid) of - [#reporter{} = Reporter] -> - do_change_reporter_status(Reporter, disabled); - [] -> ok + [#reporter{} = Reporter] -> + do_change_reporter_status(Reporter, disabled); + [] -> ok end, {noreply, St}; handle_cast(_Msg, State) -> @@ -1002,19 +1002,19 @@ handle_cast(_Msg, State) -> %%-------------------------------------------------------------------- handle_info({start_interval, Reporter, Name}, #st{} = St) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{intervals = Ints, status = enabled}] -> - case lists:keyfind(Name, #interval.name, Ints) of - #interval{} = I -> - I1 = do_start_interval_timer(I, Reporter), - ets:update_element(?EXOMETER_REPORTERS, Reporter, - [{#reporter.intervals, - lists:keyreplace( - Name, #interval.name, Ints, I1)}]); - false -> - ok - end; - _ -> - ok + [#reporter{intervals = Ints, status = enabled}] -> + case lists:keyfind(Name, #interval.name, Ints) of + #interval{} = I -> + I1 = do_start_interval_timer(I, Reporter), + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, + lists:keyreplace( + Name, #interval.name, Ints, I1)}]); + false -> + ok + end; + _ -> + ok end, {noreply, St}; handle_info({report_batch, Reporter, Name}, #st{} = St) -> @@ -1037,20 +1037,20 @@ handle_info({report, #key{} = Key, Interval, TS}, #st{} = St) -> handle_info({'DOWN', Ref, process, _Pid, Reason}, #st{} = S) -> case reporter_by_mref(Ref) of - [#reporter{module = Module, restart = Restart} = R] -> - case add_restart(Restart) of - {remove, How} -> - case How of - {M, F} when is_atom(M), is_atom(F) -> - try M:F(Module, Reason) catch _:_ -> ok end; - _ -> - ok - end, - S; - {restart, Restart1} -> - restart_reporter(R#reporter{restart = Restart1}) - end; - _ -> S + [#reporter{module = Module, restart = Restart} = R] -> + case add_restart(Restart) of + {remove, How} -> + case How of + {M, F} when is_atom(M), is_atom(F) -> + try M:F(Module, Reason) catch _:_ -> ok end; + _ -> + ok + end, + S; + {restart, Restart1} -> + restart_reporter(R#reporter{restart = Restart1}) + end; + _ -> S end, {noreply, S}; @@ -1061,138 +1061,138 @@ handle_info(_Info, State) -> restart_reporter(#reporter{name = Name, opts = Opts, restart = Restart}) -> {Pid, MRef} = spawn_reporter(Name, Opts), [resubscribe(S) || - S <- ets:select(?EXOMETER_SUBS, - [{#subscriber{key = #key{reporter = Name, - _ = '_'}, - _ = '_'}, [], ['$_']}])], + S <- ets:select(?EXOMETER_SUBS, + [{#subscriber{key = #key{reporter = Name, + _ = '_'}, + _ = '_'}, [], ['$_']}])], ets:update_element(?EXOMETER_REPORTERS, Name, - [{#reporter.pid, Pid}, - {#reporter.mref, MRef}, - {#reporter.restart, Restart}, - {#reporter.status, enabled}]), + [{#reporter.pid, Pid}, + {#reporter.mref, MRef}, + {#reporter.restart, Restart}, + {#reporter.status, enabled}]), ok. %% If there are already subscriptions, enable them. maybe_enable_subscriptions(#exometer_entry{name = Metric}) -> lists:foreach( fun(#subscriber{key = #key{reporter = RName}} = S) -> - case get_reporter_status(RName) of - enabled -> - resubscribe(S); - _ -> - ok - end + case get_reporter_status(RName) of + enabled -> + resubscribe(S); + _ -> + ok + end end, ets:select(?EXOMETER_SUBS, - [{#subscriber{key = #key{metric = Metric, - _ = '_'}, - _ = '_'}, [], ['$_']}])). + [{#subscriber{key = #key{metric = Metric, + _ = '_'}, + _ = '_'}, [], ['$_']}])). resubscribe(#subscriber{key = #key{reporter = RName, - metric = Metric, - datapoint = DataPoint, - extra = Extra} = Key, - t_ref = OldTRef, - interval = Interval}) when is_integer(Interval) -> + metric = Metric, + datapoint = DataPoint, + extra = Extra} = Key, + t_ref = OldTRef, + interval = Interval}) when is_integer(Interval) -> try_send(RName, {exometer_subscribe, Metric, DataPoint, Interval, Extra}), cancel_timer(OldTRef), TRef = erlang:send_after(Interval, self(), - subscr_timer_msg(Key, Interval)), + subscr_timer_msg(Key, Interval)), ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]); resubscribe(_) -> undefined. handle_report(#key{reporter = Reporter} = Key, Interval, TS, #st{} = St) -> _ = case ets:member(?EXOMETER_SUBS, Key) andalso - get_reporter_status(Reporter) == enabled of - true -> - case do_report(Key, Interval) of - true -> restart_subscr_timer(Key, Interval, TS); - false -> ok - end; - false -> - %% Possibly an unsubscribe removed the subscriber - ?error("No such subscriber (Key=~p)~n", [Key]) - end, + get_reporter_status(Reporter) == enabled of + true -> + case do_report(Key, Interval) of + true -> restart_subscr_timer(Key, Interval, TS); + false -> ok + end; + false -> + %% Possibly an unsubscribe removed the subscriber + ?error("No such subscriber (Key=~p)~n", [Key]) + end, St. do_report(#key{metric = Metric, - datapoint = DataPoint, - retry_failed_metrics = RetryFailedMetrics} = Key, Interval) -> + datapoint = DataPoint, + retry_failed_metrics = RetryFailedMetrics} = Key, Interval) -> case {RetryFailedMetrics, get_values(Metric, DataPoint)} of - %% We found a value, or values. - {_, [_|_] = Found} -> - %% Distribute metric value to the correct process - report_values(Found, Key), - true; - %% We did not find a value, but we should try again. - {true, _ } -> - if is_list(Metric) -> - ?debug("Metric(~p) Datapoint(~p) not found." - " Will try again in ~p msec~n", - [Metric, DataPoint, Interval]), - true; - true -> false - end; - %% We did not find a value, and we should not retry. - _ -> - %% Entry removed while timer in progress. - ?warning("Metric(~p) Datapoint(~p) not found. Will not try again~n", - [Metric, DataPoint]), - false + %% We found a value, or values. + {_, [_|_] = Found} -> + %% Distribute metric value to the correct process + report_values(Found, Key), + true; + %% We did not find a value, but we should try again. + {true, _ } -> + if is_list(Metric) -> + ?debug("Metric(~p) Datapoint(~p) not found." + " Will try again in ~p msec~n", + [Metric, DataPoint, Interval]), + true; + true -> false + end; + %% We did not find a value, and we should not retry. + _ -> + %% Entry removed while timer in progress. + ?warning("Metric(~p) Datapoint(~p) not found. Will not try again~n", + [Metric, DataPoint]), + false end. report_batch(Reporter, Name, T0) when is_atom(Name) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{status = disabled}] -> - false; - [R] -> - Entries = ets:select(?EXOMETER_SUBS, - [{#subscriber{key = #key{reporter = Reporter, - _ = '_'}, - interval = Name, - _ = '_'}, [], ['$_']}]), - lists:foreach( - fun(#subscriber{key = Key}) -> - do_report(Key, Name) - end, Entries), - restart_batch_timer(Name, R, T0); - [] -> - false + [#reporter{status = disabled}] -> + false; + [R] -> + Entries = ets:select(?EXOMETER_SUBS, + [{#subscriber{key = #key{reporter = Reporter, + _ = '_'}, + interval = Name, + _ = '_'}, [], ['$_']}]), + lists:foreach( + fun(#subscriber{key = Key}) -> + do_report(Key, Name) + end, Entries), + restart_batch_timer(Name, R, T0); + [] -> + false end. cancel_subscr_timers(Reporter) -> lists:foreach( fun(#subscriber{key = Key, t_ref = TRef}) -> - cancel_timer(TRef), - ets:update_element(?EXOMETER_SUBS, Key, - [{#subscriber.t_ref, undefined}]) + cancel_timer(TRef), + ets:update_element(?EXOMETER_SUBS, Key, + [{#subscriber.t_ref, undefined}]) end, ets:select(?EXOMETER_SUBS, - [{#subscriber{key = #key{reporter = Reporter, - _ = '_'}, - _ = '_'}, [], ['$_']}])). + [{#subscriber{key = #key{reporter = Reporter, + _ = '_'}, + _ = '_'}, [], ['$_']}])). restart_subscr_timer(Key, Interval, T0) when is_integer(Interval) -> TRef = erlang:send_after(adjust_interval(Interval, T0), self(), - subscr_timer_msg(Key, Interval, T0)), + subscr_timer_msg(Key, Interval, T0)), ets:update_element(?EXOMETER_SUBS, Key, - [{#subscriber.t_ref, TRef}]); + [{#subscriber.t_ref, TRef}]); restart_subscr_timer(_, _, _) -> true. restart_batch_timer(Name, #reporter{name = Reporter, - intervals = Ints}, T0) when is_list(Ints) -> + intervals = Ints}, T0) when is_list(Ints) -> case lists:keyfind(Name, #interval.name, Ints) of - #interval{time = Time} = I -> - TRef = erlang:send_after( - adjust_interval(Time, T0), self(), - batch_timer_msg(Reporter, Name, Time, T0)), - ets:update_element(?EXOMETER_REPORTERS, Reporter, - [{#reporter.intervals, - lists:keyreplace(Name, #interval.name, Ints, - I#interval{t_ref = TRef})}]); - false -> - false + #interval{time = Time} = I -> + TRef = erlang:send_after( + adjust_interval(Time, T0), self(), + batch_timer_msg(Reporter, Name, Time, T0)), + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, + lists:keyreplace(Name, #interval.name, Ints, + I#interval{t_ref = TRef})}]); + false -> + false end. adjust_interval(Time, T0) -> @@ -1252,14 +1252,14 @@ code_change(_OldVan, #st{reporters = Rs, subscribers = Ss} = S, _Extra) -> #reporter{name = Module, pid = Pid, mref = MRef, module = Module, opts = Opts, restart = Restart}; - ({reporter,Name,Pid,MRef,Module,Opts,Restart}) -> - #reporter{name = Name, pid = Pid, mref = MRef, - module = Module, opts = Opts, - restart = Restart}; - ({reporter,Name,Pid,Mref,Module,Opts,Restart,Status}) -> - #reporter{name = Name, pid = Pid, mref = Mref, - module = Module, opts = Opts, - restart = Restart, status = Status}; + ({reporter,Name,Pid,MRef,Module,Opts,Restart}) -> + #reporter{name = Name, pid = Pid, mref = MRef, + module = Module, opts = Opts, + restart = Restart}; + ({reporter,Name,Pid,Mref,Module,Opts,Restart,Status}) -> + #reporter{name = Name, pid = Pid, mref = Mref, + module = Module, opts = Opts, + restart = Restart, status = Status}; (#reporter{} = R) -> R end, Rs), [ets:insert(?EXOMETER_REPORTERS, R) || R <- Rs1], @@ -1274,22 +1274,22 @@ code_change(_OldVsn, State, _Extra) -> reporter_pids() -> ets:select(?EXOMETER_REPORTERS, - [{#reporter{pid = '$1', _ = '_'}, - [{is_pid,'$1'}], ['$1']}]). + [{#reporter{pid = '$1', _ = '_'}, + [{is_pid,'$1'}], ['$1']}]). reporter_by_pid(Pid) -> ets:select(?EXOMETER_REPORTERS, - [{#reporter{pid = Pid, _='_'}, [], ['$_']}]). + [{#reporter{pid = Pid, _='_'}, [], ['$_']}]). reporter_by_mref(Ref) -> ets:select(?EXOMETER_REPORTERS, - [{#reporter{mref = Ref, _='_'}, [], ['$_']}]). + [{#reporter{mref = Ref, _='_'}, [], ['$_']}]). try_send(To, Msg) -> try To ! Msg catch - error:_ -> - Msg + error:_ -> + Msg end. is_valid_metric({find, Name}, _DataPoint) when is_list(Name) -> @@ -1297,25 +1297,25 @@ is_valid_metric({find, Name}, _DataPoint) when is_list(Name) -> is_valid_metric({select, Name}, _DataPoint) when is_list(Name) -> try ets:match_spec_compile(Name), true catch - error:_ -> error + error:_ -> error end; is_valid_metric(Name, default) when is_list(Name) -> case exometer:info(Name, type) of - undefined -> false; - _ -> true + undefined -> false; + _ -> true end; is_valid_metric(Name, DataPoint) when is_list(Name) -> case dp_list(DataPoint) of - [] -> error; - [_|_] = DataPoints -> - case exometer:info(Name, datapoints) of - undefined -> false; - DPs -> - case DataPoints -- DPs of - [] -> true; - _ -> false - end - end + [] -> error; + [_|_] = DataPoints -> + case exometer:info(Name, datapoints) of + undefined -> false; + DPs -> + case DataPoints -- DPs of + [] -> true; + _ -> false + end + end end; is_valid_metric(_, _) -> false. @@ -1331,25 +1331,25 @@ dp_list(999) -> [999]. get_values(Name, DataPoint) when is_list(Name) -> case exometer:get_value(Name, DataPoint) of - {ok, Values} when is_list(Values) -> - [{Name, Values}]; - _ -> - [] + {ok, Values} when is_list(Values) -> + [{Name, Values}]; + _ -> + [] end; get_values({How, Path}, DataPoint) -> Entries = case How of - find -> exometer:find_entries(Path); - select -> exometer:select(Path) - end, + find -> exometer:find_entries(Path); + select -> exometer:select(Path) + end, lists:foldr( fun({Name, _, enabled}, Acc) -> - case exometer:get_value(Name, DataPoint) of - {ok, Values} when is_list(Values) -> - [{Name, Values}|Acc]; - _ -> - Acc - end; - (_, Acc) -> Acc + case exometer:get_value(Name, DataPoint) of + {ok, Values} when is_list(Values) -> + [{Name, Values}|Acc]; + _ -> + Acc + end; + (_, Acc) -> Acc end, [], Entries). @@ -1366,7 +1366,7 @@ spawn_reporter(Reporter, Opt) when is_atom(Reporter), is_list(Opt) -> Fun = fun() -> maybe_register(Reporter, Opt), {ok, Mod, St} = reporter_init(Reporter, Opt), - reporter_loop(Mod, St) + reporter_loop(Mod, St) end, Pid = proc_lib:spawn(Fun), MRef = erlang:monitor(process, Pid), @@ -1394,17 +1394,17 @@ terminate_reporter(#reporter{pid = undefined}) -> subscribe_(Reporter, Metric, DataPoint, Interval, RetryFailedMetrics, - Extra, Status) -> + Extra, Status) -> Key = #key{reporter = Reporter, - metric = Metric, - datapoint = DataPoint, - extra = Extra, - retry_failed_metrics = RetryFailedMetrics - }, + metric = Metric, + datapoint = DataPoint, + extra = Extra, + retry_failed_metrics = RetryFailedMetrics + }, ets:insert(?EXOMETER_SUBS, - #subscriber{key = Key, - interval = Interval, - t_ref = maybe_send_after(Status, Key, Interval)}). + #subscriber{key = Key, + interval = Interval, + t_ref = maybe_send_after(Status, Key, Interval)}). maybe_send_after(enabled, Key, Interval) when is_integer(Interval) -> erlang:send_after( @@ -1416,19 +1416,19 @@ unsubscribe_(Reporter, Metric, DataPoint, Extra) -> ?info("unsubscribe_(~p, ~p, ~p, ~p)~n", [ Reporter, Metric, DataPoint, Extra]), case ets:lookup(?EXOMETER_SUBS, #key{reporter = Reporter, - metric = Metric, - datapoint = DataPoint, - extra = Extra}) of - [#subscriber{} = Sub] -> - unsubscribe_(Sub); + metric = Metric, + datapoint = DataPoint, + extra = Extra}) of + [#subscriber{} = Sub] -> + unsubscribe_(Sub); [] -> not_found end. unsubscribe_(#subscriber{key = #key{reporter = Reporter, - metric = Metric, - datapoint = DataPoint, - extra = Extra} = Key, t_ref = TRef}) -> + metric = Metric, + datapoint = DataPoint, + extra = Extra} = Key, t_ref = TRef}) -> try_send( Reporter, {exometer_unsubscribe, Metric, DataPoint, Extra}), cancel_timer(TRef), @@ -1438,12 +1438,12 @@ unsubscribe_(#subscriber{key = #key{reporter = Reporter, report_values(Found, #key{reporter = Reporter, extra = Extra} = Key) -> try - [[report_value(Reporter, Name, DP, Extra, Val) - || {DP, Val} <- Values] || {Name, Values} <- Found] + [[report_value(Reporter, Name, DP, Extra, Val) + || {DP, Val} <- Values] || {Name, Values} <- Found] catch - error:Reason -> - lager:error("ERROR ~p~nKey = ~p~nTrace: ~p", - [Reason, Key, erlang:get_stacktrace()]) + error:Reason -> + lager:error("ERROR ~p~nKey = ~p~nTrace: ~p", + [Reason, Key, erlang:get_stacktrace()]) end. report_value(Reporter, Metric, DataPoint, Extra, Val) -> @@ -1456,9 +1456,9 @@ report_value(Reporter, Metric, DataPoint, Extra, Val) -> retrieve_metric({Metric, Type, Enabled}, Acc) -> Cands = ets:select( - ?EXOMETER_SUBS, - [{#subscriber{key = #key{metric = Metric, _='_'}, - _ = '_'}, [], ['$_']}]), + ?EXOMETER_SUBS, + [{#subscriber{key = #key{metric = Metric, _='_'}, + _ = '_'}, [], ['$_']}]), [ { Metric, exometer:info(Metric, datapoints), get_subscribers(Metric, Type, Enabled, Cands), Enabled } | Acc ]. @@ -1475,12 +1475,12 @@ get_subscribers(_Metric, _Type, _Status, []) -> %% This subscription matches Metric get_subscribers(Metric, Type, Status, - [ #subscriber { - key = #key { - reporter = SReporter, - metric = Metric, - datapoint = SDataPoint - }} | T ]) -> + [ #subscriber { + key = #key { + reporter = SReporter, + metric = Metric, + datapoint = SDataPoint + }} | T ]) -> ?debug("get_subscribers(~p, ~p, ~p): match~n", [ Metric, SDataPoint, SReporter]), [ { SReporter, SDataPoint } | get_subscribers(Metric, Type, Status, T) ]; @@ -1501,14 +1501,14 @@ get_subscribers(Metric, Type, Status, %% This subscription does not match Metric. get_subscribers(Metric, Type, Status, - [ #subscriber { - key = #key { - reporter = SReporter, - metric = SMetric, - datapoint = SDataPoint - }} | T]) -> + [ #subscriber { + key = #key { + reporter = SReporter, + metric = SMetric, + datapoint = SDataPoint + }} | T]) -> ?debug("get_subscribers(~p, ~p, ~p) nomatch(~p) ~n", - [ SMetric, SDataPoint, SReporter, Metric]), + [ SMetric, SDataPoint, SReporter, Metric]), get_subscribers(Metric, Type, Status, T). %% Purge all subscriptions associated with a specific reporter @@ -1520,12 +1520,12 @@ purge_subscriptions(R) -> %% Return new #subscriber list with all original subscribers %% that do not reference reporter R. Subs = ets:select(?EXOMETER_SUBS, - [{#subscriber{key = #key{reporter = R, _='_'}, - _ = '_'}, [], ['$_']}]), + [{#subscriber{key = #key{reporter = R, _='_'}, + _ = '_'}, [], ['$_']}]), lists:foreach(fun(#subscriber {key = Key, t_ref = TRef}) -> - cancel_timer(TRef), - ets:delete(?EXOMETER_SUBS, Key) - end, Subs). + cancel_timer(TRef), + ets:delete(?EXOMETER_SUBS, Key) + end, Subs). %% Called by the spawn_monitor() call in init %% Loop and run reporters. @@ -1534,7 +1534,7 @@ reporter_init(Reporter, Opts) -> Module = proplists:get_value(module, Opts, Reporter), case Module:exometer_init(Opts) of {ok, St} -> - {ok, Module, St}; + {ok, Module, St}; {error, Reason} -> ?error("Failed to start reporter ~p: ~p~n", [Module, Reason]), exit(Reason) @@ -1611,23 +1611,23 @@ cast(Req) -> init_subscriber({Reporter, Metric, DataPoint, Interval, RetryFailedMetrics}) -> Status = get_reporter_status(Reporter), subscribe_(Reporter, Metric, DataPoint, Interval, - RetryFailedMetrics, undefined, Status); + RetryFailedMetrics, undefined, Status); init_subscriber({Reporter, Metric, DataPoint, Interval, - RetryFailedMetrics, Extra}) -> + RetryFailedMetrics, Extra}) -> Status = get_reporter_status(Reporter), subscribe_(Reporter, Metric, DataPoint, Interval, - RetryFailedMetrics, Extra, Status); + RetryFailedMetrics, Extra, Status); init_subscriber({Reporter, Metric, DataPoint, Interval}) -> Status = get_reporter_status(Reporter), subscribe_(Reporter, Metric, DataPoint, Interval, - true, undefined, Status); + true, undefined, Status); init_subscriber({apply, {M, F, A}}) -> lists:foreach(fun(Sub) -> - init_subscriber(Sub) - end, apply(M, F, A)); + init_subscriber(Sub) + end, apply(M, F, A)); init_subscriber({select, Expr}) when tuple_size(Expr)==3; - tuple_size(Expr)==4; - tuple_size(Expr)==5 -> + tuple_size(Expr)==4; + tuple_size(Expr)==5 -> {Pattern, Reporter, DataPoint, Interval, Retry, Extra} = case Expr of {P, R, D, I} -> {P, R, D, I, true, undefined}; @@ -1639,23 +1639,23 @@ init_subscriber({select, Expr}) when tuple_size(Expr)==3; lists:foreach( fun({Entry, _, _}) -> subscribe_(Reporter, Entry, DataPoint, Interval, - Retry, Extra, Status) + Retry, Extra, Status) end, Entries); init_subscriber(Other) -> ?warning("Incorrect static subscriber spec ~p. " "Use { Reporter, Metric, DataPoint, Interval [, Extra ]}~n", - [ Other ]). + [ Other ]). get_reporter_status(R) -> try ets:lookup_element(?EXOMETER_REPORTERS, R, #reporter.status) catch - error:_ -> disabled + error:_ -> disabled end. add_restart(#restart{spec = Spec, - history = H, - save_n = N} = R) -> + history = H, + save_n = N} = R) -> T = exometer_util:timestamp(), H1 = lists:sublist([T|H], 1, N), case match_frequency(H1, Spec) of @@ -1709,10 +1709,10 @@ restart_rec(L) -> Save = lists:foldl( fun ({R,_}, Acc) when is_integer(R) -> - erlang:max(R, Acc); + erlang:max(R, Acc); (_, Acc) -> - Acc - end, 0, L), + Acc + end, 0, L), #restart{spec = L, save_n = Save}. valid_restart(L) when is_list(L) -> @@ -1730,37 +1730,37 @@ do_remove_reporter(Reporter) -> do_remove_reporter(Reporter, Terminate) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [#reporter{} = R] -> + [#reporter{} = R] -> case Terminate of true -> terminate_reporter(R); false -> ok end, - ets:delete(?EXOMETER_REPORTERS, Reporter), - purge_subscriptions(Reporter), - ok; + ets:delete(?EXOMETER_REPORTERS, Reporter), + purge_subscriptions(Reporter), + ok; [] -> {error, not_found} end. change_reporter_status(Reporter, New) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of - [R] -> do_change_reporter_status(R, New); - [] -> {error, not_found} + [R] -> do_change_reporter_status(R, New); + [] -> {error, not_found} end. do_change_reporter_status(#reporter{name = Reporter, - status = Old} = R, New) -> + status = Old} = R, New) -> case {Old, New} of - {disabled, enabled} -> - restart_reporter(R); - {enabled, disabled} -> - cancel_subscr_timers(Reporter), - terminate_reporter(R), - ets:update_element(?EXOMETER_REPORTERS, - Reporter, [{#reporter.status, disabled}]); - {Old, Old} -> - ok + {disabled, enabled} -> + restart_reporter(R); + {enabled, disabled} -> + cancel_subscr_timers(Reporter), + terminate_reporter(R), + ets:update_element(?EXOMETER_REPORTERS, + Reporter, [{#reporter.status, disabled}]); + {Old, Old} -> + ok end, ok. diff --git a/src/exometer_shallowtree.erl b/src/exometer_shallowtree.erl index ded57e3..354bbf0 100644 --- a/src/exometer_shallowtree.erl +++ b/src/exometer_shallowtree.erl @@ -12,20 +12,20 @@ -module(exometer_shallowtree). -export([new/1, - insert/3, - take_min/1, - to_list/1, - filter/2, - size/1, - limit/1]). + insert/3, + take_min/1, + to_list/1, + filter/2, + size/1, + limit/1]). -export([fill/1, fill1/2]). -export_type([tree/0]). -record(t, {size = 0, - limit = 10, - tree = []}). + limit = 10, + tree = []}). -type tree() :: #t{}. @@ -52,11 +52,11 @@ limit(#t{limit = L}) -> %% @end insert(K, V, #t{size = X, limit = X, tree = Tr} = T) when is_number(K) -> case K =< element(1, Tr) of - true -> - T; - false -> - {_, _, Tr1} = take_min_(Tr), - T#t{tree = insert_(K, V, Tr1)} + true -> + T; + false -> + {_, _, Tr1} = take_min_(Tr), + T#t{tree = insert_(K, V, Tr1)} end; insert(K, V, #t{size = Sz, tree = Tr} = T) when is_number(K) -> T#t{size = Sz+1, tree = insert_(K, V, Tr)}. @@ -71,9 +71,9 @@ insert_(K, V, T ) -> meld(mknode(K, V), T). %% @end take_min(#t{size = Sz, tree = Tr} = T) -> case take_min_(Tr) of - error -> error; - {K, V, Tr1} -> - {K, V, T#t{size = Sz-1, tree = Tr1}} + error -> error; + {K, V, Tr1} -> + {K, V, T#t{size = Sz-1, tree = Tr1}} end. take_min_([]) -> error; @@ -99,16 +99,16 @@ filter(F, #t{tree = T}) -> filter_(F, [T]). filter_(_, []) -> []; filter_(F, [{K,V,_,L,R}|T]) -> case F(K,V) of false -> filter_(F, [L,R|T]); - {true, Keep} -> [Keep|filter_(F, [L,R|T])] + {true, Keep} -> [Keep|filter_(F, [L,R|T])] end; filter_(F, [[]|T]) -> filter_(F, T). meld({K1,V1, _, L1, R1} = T1, {K2,V2, _, L2, R2} = T2) -> case K1 < K2 of - true -> - mknode(K1,V1, L1, meld(R1, T2)); - false -> - mknode(K2,V2, L2, meld(R2, T1)) + true -> + mknode(K1,V1, L1, meld(R1, T2)); + false -> + mknode(K2,V2, L2, meld(R2, T1)) end; meld([], T2) -> T2; meld(T1, []) -> T1; diff --git a/src/exometer_slide.erl b/src/exometer_slide.erl index 4f9b4de..a712431 100644 --- a/src/exometer_slide.erl +++ b/src/exometer_slide.erl @@ -178,7 +178,7 @@ to_list(#slide{size = Sz, n = N, max_n = MaxN, buf1 = Buf1, buf2 = Buf2}) -> foldl(_Timestamp, _Fun, _Acc, #slide{size = Sz}) when Sz == 0 -> []; foldl(Timestamp, Fun, Acc, #slide{size = Sz, n = N, max_n = MaxN, - buf1 = Buf1, buf2 = Buf2}) -> + buf1 = Buf1, buf2 = Buf2}) -> Start = Timestamp - Sz, lists:foldr( Fun, lists:foldl(Fun, Acc, take_since( diff --git a/src/exometer_slot_slide.erl b/src/exometer_slot_slide.erl index eb3f381..e3ac953 100644 --- a/src/exometer_slot_slide.erl +++ b/src/exometer_slot_slide.erl @@ -177,7 +177,7 @@ -export([new/2, new/4, new/5, add_element/2, add_element/3, - add_element/4, + add_element/4, reset/1, to_list/1, foldl/3, @@ -265,7 +265,7 @@ add_element(TS, Val, #slide{cur_slot = CurrentSlot, %% Invoke the sample MFA to get a new state to work with %% ret(Wrap, Flag, - Slide1#slide {cur_state = SampleF(TS, Val, Slide1#slide.cur_state)}). + Slide1#slide {cur_state = SampleF(TS, Val, Slide1#slide.cur_state)}). ret(false, _, Slide) -> Slide; @@ -313,8 +313,8 @@ foldr(Fun, Acc, Slide) -> %% within the timespan ranging from Oldest up to the current timme. %% take_since(Oldest, #slide{%% cur_slot = CurrentSlot, - cur_state = CurrentState, - slot_period = SlotPeriod } =Slide) -> + cur_state = CurrentState, + slot_period = SlotPeriod } =Slide) -> %% Check if we need to add a slot for the current time period %% before we start to grab data. @@ -325,9 +325,9 @@ take_since(Oldest, #slide{%% cur_slot = CurrentSlot, #slide { list1 = List1, list2 = List2} = %% if TSSlot =/= CurrentSlot, CurrentState =/= undefined -> - if CurrentState =/= undefined -> + if CurrentState =/= undefined -> {_, Sl} = add_slot(TS, Slide), - Sl; + Sl; true -> Slide end, @@ -368,7 +368,7 @@ add_slot(TS, #slide{timespan = TimeSpan, undefined -> %% Transformation function could not produce an element %% Reset the time slot to the current slot. Reset state./ {false, Slide#slide{ cur_slot = TSSlot, - cur_state = undefined}}; + cur_state = undefined}}; %% The transform function produced an element to store Element -> @@ -380,16 +380,16 @@ add_slot(TS, #slide{timespan = TimeSpan, %% Shift list1 into list2. %% Add the new slot as the initial element of list1 {true, Slide#slide{ list1 = [{ CurrentSlot, Element }], - list2 = List1, - list1_start_slot = CurrentSlot, - cur_slot = TSSlot, - cur_state = undefined}}; + list2 = List1, + list1_start_slot = CurrentSlot, + cur_slot = TSSlot, + cur_state = undefined}}; true -> %% No shift necessary. Tack on the new slot to list1. {false, - Slide#slide{list1 = [{CurrentSlot, Element} | List1], - cur_slot = TSSlot, - cur_state = undefined}} + Slide#slide{list1 = [{CurrentSlot, Element} | List1], + cur_slot = TSSlot, + cur_state = undefined}} end end. diff --git a/src/exometer_spiral.erl b/src/exometer_spiral.erl index a4bbb9d..17aadce 100644 --- a/src/exometer_spiral.erl +++ b/src/exometer_spiral.erl @@ -13,16 +13,16 @@ %% exometer_entry callbacks -export([behaviour/0, - probe_init/3, - probe_terminate/1, - probe_setopts/3, - probe_update/2, - probe_get_value/2, - probe_get_datapoints/1, - probe_reset/1, - probe_code_change/3, - probe_sample/1, - probe_handle_msg/2]). + probe_init/3, + probe_terminate/1, + probe_setopts/3, + probe_update/2, + probe_get_value/2, + probe_get_datapoints/1, + probe_reset/1, + probe_code_change/3, + probe_sample/1, + probe_handle_msg/2]). %% exometer_proc callback -export([count_sample/3, @@ -64,7 +64,7 @@ probe_init(Name, _Type, Options) -> {ok, St#st{slide = Slide}}. probe_terminate(_St) -> - ok. + ok. probe_get_value(DataPoints, St) -> {ok, [get_single_value(St, DataPoint) || DataPoint <- DataPoints]}. @@ -77,8 +77,8 @@ probe_setopts(_Entry, _Options, _St) -> probe_update(Increment, #st{slide = Slide, total = Total} = St) -> {ok, St#st{ - slide = exometer_slot_slide:add_element(Increment, Slide), - total = Total + Increment}}. + slide = exometer_slot_slide:add_element(Increment, Slide), + total = Total + Increment}}. probe_reset(#st{slide = Slide} = St) -> {ok, St#st{total = 0, slide = exometer_slot_slide:reset(Slide)}}. @@ -104,14 +104,14 @@ process_opts(St, Options) -> %% Unknown option, pass on to State options list, replacing %% any earlier versions of the same option. ({Opt, Val}, St1) -> - St1#st{opts = [{Opt, Val} - | lists:keydelete(Opt, 1, St1#st.opts)]} - end, St, Options). + St1#st{opts = [{Opt, Val} + | lists:keydelete(Opt, 1, St1#st.opts)]} + end, St, Options). %% Simple sample processor that maintains a counter. %% of all count_sample(_TS, Increment, undefined) -> - Increment; + Increment; count_sample(_TS, Increment, Total) -> Total + Increment. @@ -130,6 +130,6 @@ get_single_value(St, count) -> get_single_value(St, one) -> {one, exometer_slot_slide:foldl(fun({_TS, Val}, Acc) -> Acc + Val end, - 0, St#st.slide) }; + 0, St#st.slide) }; get_single_value(_St, Unsupported) -> {Unsupported, {error, unsupported}}. diff --git a/src/exometer_uniform.erl b/src/exometer_uniform.erl index 79bae21..d716461 100644 --- a/src/exometer_uniform.erl +++ b/src/exometer_uniform.erl @@ -14,7 +14,7 @@ %% exometer_probe callbacks -export([behaviour/0, - probe_init/3, + probe_init/3, probe_terminate/1, probe_get_value/2, probe_get_datapoints/1, @@ -67,9 +67,9 @@ probe_terminate(ModSt) -> probe_get_value(DataPoints, St) -> {Length, Total, Lst} = ets:foldl( - fun(#elem { val = Val }, {Length, Total, List}) -> - { Length + 1, Total + Val, [ Val | List ]} end, - {0, 0.0, []}, St#st.ets_ref), + fun(#elem { val = Val }, {Length, Total, List}) -> + { Length + 1, Total + Val, [ Val | List ]} end, + {0, 0.0, []}, St#st.ets_ref), Sorted = lists:sort(Lst), Mean = case Length of @@ -124,9 +124,9 @@ process_opts(St, Options) -> %% Unknown option, pass on to State options list, replacing %% any earlier versions of the same option. ({Opt, Val}, St1) -> - St1#st{ opts = [ {Opt, Val} - | lists:keydelete(Opt, 1, St1#st.opts) ] } - end, St, Options). + St1#st{ opts = [ {Opt, Val} + | lists:keydelete(Opt, 1, St1#st.opts) ] } + end, St, Options). probe_handle_msg(_, S) -> diff --git a/src/exometer_util.erl b/src/exometer_util.erl index add0006..d12d32c 100644 --- a/src/exometer_util.erl +++ b/src/exometer_util.erl @@ -59,7 +59,7 @@ timestamp() -> (MS-1258)*1000000000 + S*1000 + US div 1000. -spec timestamp_to_datetime(timestamp()) -> - {calendar:datetime(), non_neg_integer()}. + {calendar:datetime(), non_neg_integer()}. %% @doc Convert timestamp to a regular datetime. %% %% The timestamp is expected @@ -77,18 +77,18 @@ get_env(Key, Default) -> {ok, Value} -> Value; _ -> - case get_env1(exometer_core, Key) of - {ok, CoreValue} -> - CoreValue; - _ -> - Default - end + case get_env1(exometer_core, Key) of + {ok, CoreValue} -> + CoreValue; + _ -> + Default + end end. get_env1(App, Key) -> case application:get_env(App, Key) of - {ok, undefined} -> undefined; - Other -> Other + {ok, undefined} -> undefined; + Other -> Other end. get_opt(K, Opts, Default) -> @@ -183,15 +183,15 @@ drop_duplicates(List0) when is_list(List0) -> List1 = lists:foldl( fun (Elem, Acc) when is_tuple(Elem) -> - case lists:keymember(element(1, Elem), 1, Acc) of - true -> - Acc; - false -> - [Elem | Acc] - end; + case lists:keymember(element(1, Elem), 1, Acc) of + true -> + Acc; + false -> + [Elem | Acc] + end; (_, Acc) -> - Acc - end, [], List0), + Acc + end, [], List0), lists:reverse(List1); drop_duplicates(Any) -> Any. @@ -207,7 +207,7 @@ histogram(Values, default) -> histogram(Values, DataPoints) -> H = histogram(Values), [DP || {K,_} = DP <- H, - lists:member(K, DataPoints)]. + lists:member(K, DataPoints)]. -spec get_statistics(Length::non_neg_integer(), Total::non_neg_integer(), @@ -251,7 +251,7 @@ get_statistics2(L, Sorted, Total, Mean) -> [{n,L}, {mean, Mean}, {total, Total} | pick_items(Sorted, Items)]. -spec pick_items([number()], [{atom() | integer(), integer()}]) -> - [{atom(), number()}]. + [{atom(), number()}]. %% @doc Pick values from specified positions in a sorted list of numbers. %% %% This function is used to extract datapoints (usually percentiles) from @@ -305,16 +305,16 @@ key_match(_, _) -> false. get_datapoints(#exometer_entry{module = exometer, - type = T}) when T==counter; + type = T}) when T==counter; T==fast_counter; T==gauge -> [value, ms_since_reset]; get_datapoints(#exometer_entry{behaviour = entry, - name = Name, module = M, - type = Type, ref = Ref}) -> + name = Name, module = M, + type = Type, ref = Ref}) -> M:get_datapoints(Name, Type, Ref); get_datapoints(#exometer_entry{behaviour = probe, - name = Name, type = Type, ref = Ref}) -> + name = Name, type = Type, ref = Ref}) -> exometer_probe:get_datapoints(Name, Type, Ref). set_call_count({M, F}, Bool) -> @@ -327,9 +327,9 @@ get_status(enabled) -> enabled; get_status(disabled) -> disabled; get_status(St) when is_integer(St) -> if St band 2#1 == 1 -> - enabled; + enabled; true -> - disabled + disabled end. set_status(enabled , enabled ) -> 1; From 35e2f61990b99de66f14aa702af5767b9f5d65c5 Mon Sep 17 00:00:00 2001 From: Grzegorz Junka Date: Mon, 16 Mar 2015 11:23:25 +0000 Subject: [PATCH 14/40] Fix #12 Metrics find and select are not retried when subscribbed to statically --- src/exometer_report.erl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/exometer_report.erl b/src/exometer_report.erl index 17927e6..a56040f 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -1126,13 +1126,10 @@ do_report(#key{metric = Metric, true; %% We did not find a value, but we should try again. {true, _ } -> - if is_list(Metric) -> - ?debug("Metric(~p) Datapoint(~p) not found." - " Will try again in ~p msec~n", - [Metric, DataPoint, Interval]), - true; - true -> false - end; + ?debug("Metric(~p) Datapoint(~p) not found." + " Will try again in ~p msec~n", + [Metric, DataPoint, Interval]), + true; %% We did not find a value, and we should not retry. _ -> %% Entry removed while timer in progress. From b494967492f5cf85158d78abc96142eee63add8d Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 12 Jan 2015 15:23:45 +0100 Subject: [PATCH 15/40] add global status check --- src/exometer.erl | 40 ++++++++++++++++++++++++++++++++++++++-- src/exometer_admin.erl | 7 ++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/exometer.erl b/src/exometer.erl index 140010c..62769d1 100644 --- a/src/exometer.erl +++ b/src/exometer.erl @@ -60,6 +60,8 @@ register_application/1 ]). +-export([global_status/1]). + -export([create_entry/1]). % called only from exometer_admin.erl %% Convenience function for testing @@ -201,7 +203,13 @@ ensure(Name, Type, Opts) when is_list(Name), is_list(Opts) -> %% corresponding callback module will be called. For a disabled metric, %% `ok' will be returned without any other action being taken. %% @end -update(Name, Value) when is_list(Name) -> +update(Name, Value) -> + case exometer_global:status() of + enabled -> update_(Name, Value); + _ -> ok + end. + +update_(Name, Value) when is_list(Name) -> case ets:lookup(Table = exometer_util:table(), Name) of [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> @@ -430,7 +438,13 @@ sample(Name) when is_list(Name) -> %% the exometer entry, which can be recalled using {@link info/2}, and will %% indicate the time that has passed since the metric was last reset. %% @end -reset(Name) when is_list(Name) -> +reset(Name) -> + case exometer_global:status() of + enabled -> reset_(Name); + _ -> ok + end. + +reset_(Name) when is_list(Name) -> case ets:lookup(exometer_util:table(), Name) of [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> case E of @@ -810,6 +824,28 @@ aggr_acc([{D,V}|T], Acc) -> aggr_acc([], Acc) -> Acc. +global_status(St) when St==enabled; St==disabled -> + Prev = exometer_global:status(), + if St =:= Prev -> ok; + true -> + parse_trans_mod:transform_module( + exometer_global, fun(Forms,_) -> pt(Forms, St) end, []) + end, + Prev. + +pt(Forms, St) -> + parse_trans:plain_transform( + fun(F) -> + plain_pt(F, St) + end, Forms). + +plain_pt({function, L, status, 0, [_]}, St) -> + {function, L, status, 0, + [{clause, L, [], [], [{atom, L, St}]}]}; +plain_pt(_, _) -> + continue. + + %% Perform variable replacement in the ets select pattern. %% We want to project the entries as a set of {Name, Type, Status} tuples. %% This means we need to perform variable substitution in both guards and diff --git a/src/exometer_admin.erl b/src/exometer_admin.erl index 29f3d4c..4239e52 100644 --- a/src/exometer_admin.erl +++ b/src/exometer_admin.erl @@ -146,7 +146,12 @@ do_load_predef(Src, L) when is_list(L) -> (Other) -> lager:error("Predef(~p): ~p~n", [Src, {bad_pattern,Other}]) - end, Found) + end, Found); + ({aliases, Aliases}) -> + lists:foreach( + fun({Alias, Entry, DP}) -> + exometer_alias:new(Alias, Entry, DP) + end, Aliases) end, L). predef_delete_entry(Key, Src) -> From 7596ad1cbaaadf5cf248bf6451b5fd0327101601 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 12 Jan 2015 23:29:26 +0100 Subject: [PATCH 16/40] add exometer_global.erl --- src/exometer_global.erl | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/exometer_global.erl diff --git a/src/exometer_global.erl b/src/exometer_global.erl new file mode 100644 index 0000000..bcb56b3 --- /dev/null +++ b/src/exometer_global.erl @@ -0,0 +1,7 @@ +-module(exometer_global). + +-export([status/0]). + + +status() -> + enabled. From beec6a89cbc44b77f4848f874a80bec416477308 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Wed, 18 Mar 2015 15:30:13 +0100 Subject: [PATCH 17/40] clean up whitespace --- src/exometer.erl | 14 +++++++------- src/exometer_admin.erl | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/exometer.erl b/src/exometer.erl index 62769d1..090c16b 100644 --- a/src/exometer.erl +++ b/src/exometer.erl @@ -205,8 +205,8 @@ ensure(Name, Type, Opts) when is_list(Name), is_list(Opts) -> %% @end update(Name, Value) -> case exometer_global:status() of - enabled -> update_(Name, Value); - _ -> ok + enabled -> update_(Name, Value); + _ -> ok end. update_(Name, Value) when is_list(Name) -> @@ -440,8 +440,8 @@ sample(Name) when is_list(Name) -> %% @end reset(Name) -> case exometer_global:status() of - enabled -> reset_(Name); - _ -> ok + enabled -> reset_(Name); + _ -> ok end. reset_(Name) when is_list(Name) -> @@ -828,15 +828,15 @@ global_status(St) when St==enabled; St==disabled -> Prev = exometer_global:status(), if St =:= Prev -> ok; true -> - parse_trans_mod:transform_module( - exometer_global, fun(Forms,_) -> pt(Forms, St) end, []) + parse_trans_mod:transform_module( + exometer_global, fun(Forms,_) -> pt(Forms, St) end, []) end, Prev. pt(Forms, St) -> parse_trans:plain_transform( fun(F) -> - plain_pt(F, St) + plain_pt(F, St) end, Forms). plain_pt({function, L, status, 0, [_]}, St) -> diff --git a/src/exometer_admin.erl b/src/exometer_admin.erl index 4239e52..4d55892 100644 --- a/src/exometer_admin.erl +++ b/src/exometer_admin.erl @@ -146,7 +146,7 @@ do_load_predef(Src, L) when is_list(L) -> (Other) -> lager:error("Predef(~p): ~p~n", [Src, {bad_pattern,Other}]) - end, Found); + end, Found); ({aliases, Aliases}) -> lists:foreach( fun({Alias, Entry, DP}) -> From caa38dcd942fb47d3807c2ded0f836ac5fed84bc Mon Sep 17 00:00:00 2001 From: Kelly McLaughlin Date: Wed, 18 Mar 2015 10:42:24 -0600 Subject: [PATCH 18/40] Add setup to the list of exometer_core applicatons The setup application is required by exometer_core when it starts so add it to the list of applications to ensure it is included in release builds. This avoids applications that include exometer_core as a dependency from having to explicitly list the setup application in a release configuration. --- src/exometer_core.app.src | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/exometer_core.app.src b/src/exometer_core.app.src index f7f549a..12d349a 100644 --- a/src/exometer_core.app.src +++ b/src/exometer_core.app.src @@ -8,7 +8,8 @@ [ kernel, stdlib, - lager + lager, + setup ]}, {included_applications, [ From 6f8fc68975b6dfcb66a60d91b4851cb45979ff8c Mon Sep 17 00:00:00 2001 From: Kelly McLaughlin Date: Wed, 18 Mar 2015 14:23:27 -0600 Subject: [PATCH 19/40] Update test setup to address test failures Use application:ensure_all_started to start all required applications for exometer_core when doing test case initialization in the common_test suites. Also stop each started application during test case cleanup. --- test/exometer_SUITE.erl | 33 ++++++++++++++++----------------- test/exometer_alias_SUITE.erl | 6 +++--- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index f65822d..a528766 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -111,50 +111,49 @@ init_per_testcase(Case, Config) when Case == test_folsom_histogram; Case == test_history1_folsom; Case == test_history4_folsom -> + {ok, StartedApps} = application:ensure_all_started(exometer_core), application:start(bear), application:start(folsom), - exometer:start(), - Config; + [{started_apps, StartedApps} | Config]; init_per_testcase(Case, Config) when Case == test_ext_predef; Case == test_function_match -> ok = application:set_env(stdlib, exometer_predefined, {script, "../../test/data/test_defaults.script"}), - ok = application:start(setup), - exometer:start(), - Config; + {ok, StartedApps} = application:ensure_all_started(exometer_core), + [{started_apps, StartedApps} | Config]; init_per_testcase(test_app_predef, Config) -> compile_app1(Config), - exometer:start(), + {ok, StartedApps} = application:ensure_all_started(exometer_core), Scr = filename:join(filename:dirname( filename:absname(?config(data_dir, Config))), "data/app1.script"), ok = application:set_env(app1, exometer_predefined, {script, Scr}), - Config; + [{started_apps, StartedApps} | Config]; init_per_testcase(_Case, Config) -> - exometer:start(), - Config. + {ok, StartedApps} = application:ensure_all_started(exometer_core), + [{started_apps, StartedApps} | Config]. -end_per_testcase(Case, _Config) when +end_per_testcase(Case, Config) when Case == test_folsom_histogram; Case == test_history1_folsom; Case == test_history4_folsom -> - exometer:stop(), + [application:stop(App) || App <- ?config(started_apps, Config)], folsom:stop(), application:stop(bear), ok; -end_per_testcase(Case, _Config) when +end_per_testcase(Case, Config) when Case == test_ext_predef; Case == test_function_match -> ok = application:unset_env(common_test, exometer_predefined), - exometer:stop(), + [application:stop(App) || App <- ?config(started_apps, Config)], ok = application:stop(setup), ok; -end_per_testcase(test_app_predef, _Config) -> +end_per_testcase(test_app_predef, Config) -> ok = application:stop(app1), - exometer:stop(), + [application:stop(App) || App <- ?config(started_apps, Config)], ok; -end_per_testcase(_Case, _Config) -> - exometer:stop(), +end_per_testcase(_Case, Config) -> + [application:stop(App) || App <- ?config(started_apps, Config)], ok. %%%=================================================================== diff --git a/test/exometer_alias_SUITE.erl b/test/exometer_alias_SUITE.erl index 8bf0f92..62c2483 100644 --- a/test/exometer_alias_SUITE.erl +++ b/test/exometer_alias_SUITE.erl @@ -68,11 +68,11 @@ end_per_suite(_Config) -> ok. init_per_testcase(Case, Config) -> - exometer:start(), - Config. + {ok, StartedApps} = application:ensure_all_started(exometer_core), + [{started_apps, StartedApps} | Config]. end_per_testcase(_Case, Config) -> - exometer:stop(), + [application:stop(App) || App <- ?config(started_apps, Config)], ok. %%%=================================================================== From ed81a189507183dce7f8cecc954808240a5ca265 Mon Sep 17 00:00:00 2001 From: Kelly McLaughlin Date: Wed, 18 Mar 2015 15:31:07 -0600 Subject: [PATCH 20/40] Handle test setup for all Erlang versions Handle test setup for Erlang versions that pre-date application:ensure_all_started/1 by providing an implmentation if one cannot be found in the application module. This code is originally from Basho's Webmachine. --- test/exometer_SUITE.erl | 10 +++++----- test/exometer_alias_SUITE.erl | 2 +- test/exometer_test_util.erl | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 test/exometer_test_util.erl diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index a528766..55b86ad 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -119,18 +119,18 @@ init_per_testcase(Case, Config) when Case == test_ext_predef; Case == test_function_match -> ok = application:set_env(stdlib, exometer_predefined, {script, "../../test/data/test_defaults.script"}), - {ok, StartedApps} = application:ensure_all_started(exometer_core), + {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), [{started_apps, StartedApps} | Config]; init_per_testcase(test_app_predef, Config) -> compile_app1(Config), - {ok, StartedApps} = application:ensure_all_started(exometer_core), + {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), Scr = filename:join(filename:dirname( - filename:absname(?config(data_dir, Config))), - "data/app1.script"), + filename:absname(?config(data_dir, Config))), + "data/app1.script"), ok = application:set_env(app1, exometer_predefined, {script, Scr}), [{started_apps, StartedApps} | Config]; init_per_testcase(_Case, Config) -> - {ok, StartedApps} = application:ensure_all_started(exometer_core), + {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), [{started_apps, StartedApps} | Config]. end_per_testcase(Case, Config) when diff --git a/test/exometer_alias_SUITE.erl b/test/exometer_alias_SUITE.erl index 62c2483..66c7c51 100644 --- a/test/exometer_alias_SUITE.erl +++ b/test/exometer_alias_SUITE.erl @@ -68,7 +68,7 @@ end_per_suite(_Config) -> ok. init_per_testcase(Case, Config) -> - {ok, StartedApps} = application:ensure_all_started(exometer_core), + {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), [{started_apps, StartedApps} | Config]. end_per_testcase(_Case, Config) -> diff --git a/test/exometer_test_util.erl b/test/exometer_test_util.erl new file mode 100644 index 0000000..c6ada43 --- /dev/null +++ b/test/exometer_test_util.erl @@ -0,0 +1,34 @@ +-module(exometer_test_util). + +-export([ensure_all_started/1]). + +%% This implementation is originally from Basho's Webmachine. On +%% older versions of Erlang, we don't have +%% application:ensure_all_started, so we use this wrapper function to +%% either use the native implementation or our own version, depending +%% on what's available. +-spec ensure_all_started(atom()) -> {ok, [atom()]} | {error, term()}. +ensure_all_started(App) -> + case erlang:function_exported(application, ensure_all_started, 1) of + true -> + application:ensure_all_started(App); + false -> + ensure_all_started(App, []) + end. + +%% This implementation is originally from Basho's +%% Webmachine. Reimplementation of ensure_all_started. NOTE this does +%% not behave the same as the native version in all cases, but as a +%% quick hack it works well enough for our purposes. Eventually I +%% assume we'll drop support for older versions of Erlang and this can +%% be eliminated. +ensure_all_started(App, Apps0) -> + case application:start(App) of + ok -> + {ok, lists:reverse([App | Apps0])}; + {error,{already_started,App}} -> + {ok, lists:reverse(Apps0)}; + {error,{not_started,BaseApp}} -> + {ok, Apps} = ensure_all_started(BaseApp, Apps0), + ensure_all_started(App, [BaseApp|Apps]) + end. From ad29eb8486063ad2bac6944e7eb4be4a2dfae9a8 Mon Sep 17 00:00:00 2001 From: Kelly McLaughlin Date: Thu, 19 Mar 2015 08:47:17 -0600 Subject: [PATCH 21/40] Update application setup for one last test case Update another init_per_testcase function clause to use exometer_test_util:ensure_all_started to try and make test setup happy for a wider range or Erlang versions. --- test/exometer_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index 55b86ad..da2c5f0 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -111,7 +111,7 @@ init_per_testcase(Case, Config) when Case == test_folsom_histogram; Case == test_history1_folsom; Case == test_history4_folsom -> - {ok, StartedApps} = application:ensure_all_started(exometer_core), + {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), application:start(bear), application:start(folsom), [{started_apps, StartedApps} | Config]; From 0202c058ac0a91670ac02a0a5c4f8e0d96c650c2 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 30 Mar 2015 22:22:23 +0200 Subject: [PATCH 22/40] clarify check of environment; add pic --- doc/exometer_overview.odp | Bin 0 -> 16661 bytes doc/exometer_overview.png | Bin 0 -> 51042 bytes doc/overview.edoc | 6 ++++++ 3 files changed, 6 insertions(+) create mode 100644 doc/exometer_overview.odp create mode 100644 doc/exometer_overview.png diff --git a/doc/exometer_overview.odp b/doc/exometer_overview.odp new file mode 100644 index 0000000000000000000000000000000000000000..1b9636e60b6ef18e7fe051784926c16a85e08014 GIT binary patch literal 16661 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 literal 0 HcmV?d00001 diff --git a/doc/exometer_overview.png b/doc/exometer_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..cedd5e90310e2c51051dca18558ef6cf081a94ea GIT binary patch literal 51042 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: Exometer Core will check both the `exometer' and the `exometer_core' +application environments. The `exometer' environment overrides the +`exometer_core' environment. However, if only `exometer_core' is used, any +`exometer' environment will simply be ignored. This is because of the +application controller: environment data is not loaded until the application +in question is loaded. === Configuring type - entry maps === From 5fc843c4685be5f644bc0105e3c86f7e387116a0 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 3 Apr 2015 13:14:45 +0200 Subject: [PATCH 23/40] Add 'manual' type to named reporting intervals The main reason for doing this is to simplify unit testing, but also interactive testing with reporters. Rather than having repeating output, or (in unit tests) having to wait for some time before output is generated, a report batch can now be triggered on demand. --- src/exometer_report.erl | 50 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/exometer_report.erl b/src/exometer_report.erl index a56040f..d709dc1 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -160,6 +160,7 @@ set_interval/3, delete_interval/2, restart_intervals/1, + trigger_interval/2, get_intervals/1, remove_reporter/1, remove_reporter/2, terminate_reporter/1, @@ -268,7 +269,7 @@ -record(interval, { name :: atom(), - time = 0 :: non_neg_integer(), + time = 0 :: non_neg_integer() | 'manual', delay = 0 :: non_neg_integer(), t_ref :: reference() | undefined }). @@ -426,12 +427,15 @@ list_subscriptions(Reporter) -> %% `{intervals, [named_interval()]}' %% named_interval() :: {Name::atom(), Interval::pos_integer()} %% | {Name::atom(), Interval::time_ms(), delay()::time_ms()} +%% | {Name::atom(), 'manual'} %% Define named intervals. The name can be used by subscribers, so that all %% subsriptions for a given named interval will be reported when the interval %% triggers. An optional delay (in ms) can be given: this will cause the first %% interval to start in `Delay' milliseconds. When all intervals are named %% at the same time, the delay parameter can be used to achieve staggered -%% reporting. +%% reporting. If the interval is specified as ```'manual'''', it will have +%% to be triggered manually using {@link trigger_interval/2}. +%% %% @end add_reporter(Reporter, Options) -> call({add_reporter, Reporter, Options}). @@ -447,7 +451,9 @@ remove_reporter(Reporter) -> %% %% See {@link add_reporter/2} for a description of named intervals. %% The named interval is here specified as either `Time' (milliseconds) or -%% `{Time, Delay}', where a delay in milliseconds is provided. +%% `{Time, Delay}', where a delay in milliseconds is provided. It is also +%% specify an interval as ```'manual'''', indicating that the interval can +%% only be triggered manually via {@link trigger_interval/2}. %% %% If the named interval exists, it will be replaced with the new definition. %% Otherwise, it will be added. Use {@link restart_intervals/1} if you want @@ -456,6 +462,8 @@ remove_reporter(Reporter) -> set_interval(Reporter, Name, Time) when is_atom(Name), is_integer(Time), Time >= 0 -> call({set_interval, Reporter, Name, Time}); +set_interval(Reporter, Name, manual) when is_atom(Name) -> + call({set_interval, Reporter, Name, manual}); set_interval(Reporter, Name, {Time, Delay}) when is_atom(Name), is_integer(Time), Time >= 0, is_integer(Delay), @@ -477,6 +485,17 @@ delete_interval(Reporter, Name) -> restart_intervals(Reporter) -> call({restart_intervals, Reporter}). +-spec trigger_interval(reporter_name(), atom()) -> ok. +%% @doc Trigger a named interval. +%% +%% This function is mainly used to trigger intervals defined as ```'manual'''', +%% but can be used to trigger any named interval. If a named interval with +%% a specified time in milliseconds is triggered this way, it will effectively +%% be restarted, and will repeat as usual from that point on. +%% @end +trigger_interval(Reporter, Name) -> + cast({trigger_interval, Reporter, Name}). + -spec get_intervals(reporter_name()) -> [{atom(), [{time, pos_integer()} | {delay, pos_integer()} @@ -673,6 +692,8 @@ get_interval_opts(Opts) -> is_integer(Time), Time >= 0, is_integer(Delay), Delay >= 0 -> #interval{name = Name, time = Time, delay = Delay}; + ({Name, manual}) when is_atom(Name) -> + #interval{name = Name, time = manual}; (Other) -> error({invalid_interval, Other}) end, Is ++ Is1). @@ -685,6 +706,8 @@ singelton_interval({N,T,D}=I) when is_atom(N), start_interval_timers(#reporter{name = R, intervals = Ints}) -> lists:map(fun(I) -> start_interval_timer(I, R) end, Ints). +start_interval_timer(#interval{time = manual} = I, _) -> + I; start_interval_timer(#interval{name = Name, delay = Delay, t_ref = Ref} = I, R) -> cancel_timer(Ref), @@ -892,7 +915,10 @@ handle_call({set_interval, Reporter, Name, Int}, _, #st{}=St) -> is_integer(Delay), Delay >= 0 -> I0#interval{time = Time, delay = Delay}; Time when is_integer(Time), Time >= 0 -> - I0#interval{time = Time} + I0#interval{time = Time}; + manual -> + cancel_timer(I0#interval.t_ref), + I0#interval{time = manual} end, ets:update_element(?EXOMETER_REPORTERS, Reporter, [{#reporter.intervals, @@ -988,6 +1014,9 @@ handle_cast({disable, Pid}, #st{} = St) -> [] -> ok end, {noreply, St}; +handle_cast({trigger_interval, Reporter, Name}, #st{} = St) -> + report_batch(Reporter, Name, os:timestamp()), + {noreply, St}; handle_cast(_Msg, State) -> {noreply, State}. @@ -1004,12 +1033,14 @@ handle_info({start_interval, Reporter, Name}, #st{} = St) -> case ets:lookup(?EXOMETER_REPORTERS, Reporter) of [#reporter{intervals = Ints, status = enabled}] -> case lists:keyfind(Name, #interval.name, Ints) of - #interval{} = I -> + #interval{time = Time} = I when is_integer(Time) -> I1 = do_start_interval_timer(I, Reporter), ets:update_element(?EXOMETER_REPORTERS, Reporter, [{#reporter.intervals, lists:keyreplace( Name, #interval.name, Ints, I1)}]); + #interval{time = manual} -> + ok; false -> ok end; @@ -1126,10 +1157,13 @@ do_report(#key{metric = Metric, true; %% We did not find a value, but we should try again. {true, _ } -> + if is_list(Metric) -> ?debug("Metric(~p) Datapoint(~p) not found." " Will try again in ~p msec~n", [Metric, DataPoint, Interval]), true; + true -> false + end; %% We did not find a value, and we should not retry. _ -> %% Entry removed while timer in progress. @@ -1180,7 +1214,7 @@ restart_subscr_timer(_, _, _) -> restart_batch_timer(Name, #reporter{name = Reporter, intervals = Ints}, T0) when is_list(Ints) -> case lists:keyfind(Name, #interval.name, Ints) of - #interval{time = Time} = I -> + #interval{time = Time} = I when is_integer(Time) -> TRef = erlang:send_after( adjust_interval(Time, T0), self(), batch_timer_msg(Reporter, Name, Time, T0)), @@ -1188,6 +1222,8 @@ restart_batch_timer(Name, #reporter{name = Reporter, [{#reporter.intervals, lists:keyreplace(Name, #interval.name, Ints, I#interval{t_ref = TRef})}]); + #interval{time = manual} -> + false; false -> false end. @@ -1201,6 +1237,8 @@ tdiff(T1, T0) -> %% Calculate time when timer should have fired, based on timestamp logged %% at send_after/3 and the intended interval (in ms). +calc_fire_time({manual, TS}, _) -> + TS; calc_fire_time({M,S,U}, Int) -> {M, S, U + (Int*1000)}. From eb1ec407dce958bc32258ab553d07dd0a4bad6da Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 3 Apr 2015 17:12:39 +0200 Subject: [PATCH 24/40] Add .dir-locals.el --- .dir-locals.el | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .dir-locals.el diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..66406bc --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,2 @@ +( (erlang-mode . ((indent-tabs-mode . nil)) ) + ) From 185ef28f017bb542ed649da16835950c13517151 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 6 Apr 2015 16:44:24 +0200 Subject: [PATCH 25/40] Added exometer_report_logger and test suite --- src/exometer_core_sup.erl | 13 +- src/exometer_report_logger.erl | 427 ++++++++++++++++++++++++++++ src/exometer_report_logger_sup.erl | 26 ++ src/exometer_util.erl | 8 + test/exometer_SUITE.erl | 15 +- test/exometer_report_SUITE.erl | 192 +++++++++++++ test/exometer_test_udp_reporter.erl | 118 ++++++++ 7 files changed, 789 insertions(+), 10 deletions(-) create mode 100644 src/exometer_report_logger.erl create mode 100644 src/exometer_report_logger_sup.erl create mode 100644 test/exometer_report_SUITE.erl create mode 100644 test/exometer_test_udp_reporter.erl diff --git a/src/exometer_core_sup.erl b/src/exometer_core_sup.erl index 44268d1..5608a7f 100644 --- a/src/exometer_core_sup.erl +++ b/src/exometer_core_sup.erl @@ -35,10 +35,11 @@ start_link() -> init([]) -> Children0 = [ - ?CHILD(exometer_admin, worker), - ?CHILD(exometer_cache, worker), - ?CHILD(exometer_report, worker), - ?CHILD(exometer_folsom_monitor, worker), - ?CHILD(exometer_alias, worker) - ], + ?CHILD(exometer_admin, worker), + ?CHILD(exometer_cache, worker), + ?CHILD(exometer_report, worker), + ?CHILD(exometer_report_logger_sup, supervisor), + ?CHILD(exometer_folsom_monitor, worker), + ?CHILD(exometer_alias, worker) + ], {ok, {{one_for_one, 5, 10}, Children0}}. diff --git a/src/exometer_report_logger.erl b/src/exometer_report_logger.erl new file mode 100644 index 0000000..bb7d729 --- /dev/null +++ b/src/exometer_report_logger.erl @@ -0,0 +1,427 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @doc Exometer report collector and logger. +%% +%% This module implements a behavior for collecting reporting data and +%% handling it (logging to disk or ets, printing to tty, etc.) +%% +%% The logger has built-in support for receiving input via UDP, TCP or +%% internal Erlang messaging, as well as a plugin API for custom input +%% handling. Correspondingly, it has support for output to TTY or ets, as +%% well as a plugin API for custom output. +%% +%% An example of how the logger can be used can be found in +%% `test/exometer_test_udp_reporter.erl', which implements a UDP-based +%% reporter as well as an input plugin and an output plugin. This reporter +%% is used by `test/exometer_report_SUITE.erl'. +%% +%% Loggers can be combined, e.g. by creating one logger that receives Erlang +%% messages, and other loggers that receive from different sources, prefix +%% their reports and pass them on to the first logger. +%% +%%

Input plugins

+%% +%% An input plugin is initiated by `Module:logger_init_input(State)', where +%% `State' is whatever was given as a `state' option (default: `undefined'). +%% The function must create a process and return `{ok, Pid}'. `Pid' is +%% responsible for setting up whatever input channel is desired, and passes +%% on incoming data to the logger via Erlang messages `{plugin, Pid, Data}'. +%% +%%

Output Chaining

+%% +%% Each incoming data item is passed through the list of output operators. +%% Each output operator is able to modify the data (the `tty' and `ets' +%% operators leave the data unchanged). Output plugins receive the data +%% in `Module:logger_handle_data(Data, State)', which must return +%% `{NewData, NewState}'. The state is private to the plugin, while `NewData' +%% will be passed along to the next output operator. +%% +%%

Flow control

+%% +%% The logger will handle flow control automatically for `udp' and `tcp' +%% inputs. If `{active,once}' or `{active, false}', the logger will trigger +%% `{active, once}' each time it has handled an incoming message. +%% If `{active, N}', it will "refill" the port each time it receives an +%% indication that it has become passive. +%% +%% Input plugins create a process in `Module:logger_init_input/1'. This process +%% can mimick the behavior of Erlang ports by sending a `{plugin_passive, Pid}' +%% message to the logger. The logger will reply with a message, +%% `{plugin_active, N}', where `N' is the value given by the `active' option. +%% @end +-module(exometer_report_logger). + +-behaviour(gen_server). + +-export([new/1]). +-export([start_link/1]). + +-export([info/0, + info/1]). + +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-record(st, {id, + input, + output}). + +-include_lib("parse_trans/include/exprecs.hrl"). +-export_records([tcp, udp, tty, ets, int]). + +%% input records +-record(tcp, {socket, port, options = [], active = once}). +-record(udp, {socket, port, options = [], active = true}). +%% output records +-record(tty, {prefix = []}). +-record(ets, {tab}). +%% both input and output +-record(int, {process}). +-record(plugin, {module, mod_state, process, active = once}). + +-type proplist() :: [{atom(), any()}]. + +-type logger_info() :: {id, any()} + | {input, proplist()} + | {output, proplist()}. + +-type plugin_state() :: any(). + +-callback logger_init_input(any()) -> + {ok, pid()}. +-callback logger_init_output(any()) -> + {ok, plugin_state()}. +-callback logger_handle_data(binary(), plugin_state()) -> + {binary(), plugin_state()}. + +-spec new([{id, any()} | {input, list()} | {output, list()}]) -> {ok,pid()}. +%% @doc Create a new logger instance. +%% +%% This function creates a logger process with the given input and output +%% parameters. +%% +%% * `{id, ID}' is mainly for documentation and simplifying identification +%% of instances returned by {@link info/0}. +%% * `{input, PropList}' specifies what the logger listens to. Only the first +%% `input' entry is regarded, but the option is mandatory. +%% * `{output, PropList}' specifies what the logger should to with received +%% data. Multiple `output' entries are allowed, and they will be processed +%% in the order given. +%% +%% Valid input options: +%% +%% * `{mode, udp | tcp | internal | plugin}' defines the protocol +%% * `{active, false | true | once | N}' provides flow control. Default: `true'. +%% * (mode-specific options) +%% +%% Valid output options: +%% +%% * `{mode, tty | ets | plugin | internal}' defines output types +%% * (output-specific options) +%% +%% Mode-specific options, `udp': +%% +%% * `{port, integer()}' - UDP port number +%% * `{options, list()}' - Options to pass to {@link gen_udp:open/2} +%% +%% Mode-specific options, `tcp': +%% +%% * `{port, integer()}' - TCP port number +%% * `{options, list()}' - Options to pass to {@link gen_tcp:listen/2} +%% +%% Mode-specific options, `tty': +%% +%% * `{prefix, iolist()}' - Prefix string inserted before the data, which is +%% printed as-is (note that any delimiter would need to be part of the prefix) +%% +%% Mode-specific options, `ets': +%% * `{table, ets:table()}' - Ets table identifier. If not specified, an +%% ordered-set table will be created by the logger process. The incoming +%% data will be inserted as `{erlang:now(), Data}'. +%% +%% Mode-specific options, `internal': +%% * `{process, PidOrRegname}' specifies another logger instance, which is to +%% receive data from this logger (if used in output), or which is allowed +%% to send to this logger (if used in input). If no process is given for +%% input, any process can send data (on the form +%% `{exometer_report_logger, Pid, Data}') to this logger. +%% +%% Mode-specific options, `plugin': +%% +%% * `{module, Module}' - name of callback module +%% (behaviour: `exometer_report_logger') +%% * `{state, State}' - Passed as initial argument to +%% `Module:logger_init_input/1' or `Module:logger_init_output/1', depending +%% on whether the plugin is specified as input or output. +%% @end +new(Options) -> + supervisor:start_child(exometer_report_logger_sup, [Options]). + +-spec start_link(proplist()) -> {ok, pid()}. +%% @doc Start function for logger instance. +%% +%% This function is the start function eventually called as a result from +%% {@link new/1}, but whereas `new/1' creates a supervised instance, this +%% function simply creates the process. It would normally not be used directly. +%% @end +start_link(Options) -> + ID = exometer_util:get_opt(id, Options, undefined), + Input = get_input(Options), + Output = get_output(Options), + gen_server:start_link(?MODULE, {ID, Input, Output}, []). + +-spec info() -> [{pid(), [logger_info()]}]. +%% @doc List active logger instances. +%% +%% This function lists the instances started via {@link new/1}, along with their +%% respective settings as nested property lists. +%% @end +info() -> + [{P, info(P)} || {_, P, _, _} <- supervisor:which_children( + exometer_report_logger_sup)]. +-spec info(pid()) -> [logger_info()]. +%% @doc Lists the settings of a given logger instance. +info(P) -> + gen_server:call(P, info). + +%% Client-side check +get_input(Opts) -> + L = exometer_util:get_opt(input, Opts), + get_input(exometer_util:get_opt(mode, L), L). + +get_input(tcp, L) -> + Port = exometer_util:get_opt(port, L), + Options = exometer_util:get_opt(options, L, []), + Active = exometer_util:get_opt( + active, L, get_opt_active(Options)), + #tcp{port = Port, options = Options, active = Active}; +get_input(udp, L) -> + Port = exometer_util:get_opt(port, L), + Options = exometer_util:get_opt(options, L, []), + Active = exometer_util:get_opt( + active, L, get_opt_active(Options)), + #udp{port = Port, options = Options, active = Active}; +get_input(internal, L) -> + P = exometer_util:get_opt(process, L, undefined), + #int{process = P}; +get_input(plugin, L) -> + Mod = exometer_util:get_opt(module, L), + St = exometer_util:get_opt(state, L, undefined), + Active = exometer_util:get_opt(active, L, true), + #plugin{module = Mod, mod_state = St, active = Active}. + + +get_opt_active(Opts) -> + case lists:keyfind(active, 1, Opts) of + {_, true} -> true; + {_, N} when is_integer(N) -> N; + _ -> once + end. + +%% Client-side check +get_output(Opts) -> + [get_output(exometer_util:get_opt(mode, O), O) || {output, O} <- Opts]. + +get_output(tty, O) -> + Prefix = exometer_util:get_opt(prefix, O, []), + #tty{prefix = Prefix}; +get_output(ets, O) -> + Tab = exometer_util:get_opt(tab, O, undefined), + #ets{tab = Tab}; +get_output(internal, O) -> + P = exometer_util:get_opt(process, O), + #int{process = P}; +get_output(plugin, O) -> + Mod = exometer_util:get_opt(module, O), + St = exometer_util:get_opt(state, O, undefined), + #plugin{module = Mod, mod_state = St}. + + +%% Gen_server callbacks + +%% @private +init({ID, Input, Output}) -> + NewL = init_input(Input), + NewO = init_output(Output), + {ok, #st{id = ID, + input = NewL, + output = NewO}}. + +%% @private +handle_call(info, _, #st{id = ID, input = I, output = O} = S) -> + {reply, info_(ID, I, O), S}; +handle_call(_Req, _From, St) -> + {reply, {error, unsupported}, St}. + +%% @private +handle_cast({socket, Socket}, #st{input = L} = S) -> + case L of + #tcp{active = N} = T -> + case inet:getopts(Socket, [active]) of + [false] -> + inet:setopts(Socket, {active, N}); + _ -> + ok + end, + {noreply, S#st{input = T#tcp{socket = Socket}}}; + _ -> + {noreply, S} + end; +handle_cast(_Msg, St) -> + {noreply, St}. + +%% @private +handle_info({tcp, Socket, Data}, #st{input = #tcp{socket = Socket, + active = Active}, + output = Out} = S) -> + handle_data(Data, Out), + check_active(Socket, Active), + {noreply, S}; +handle_info({udp, Socket, _Host, _Port, Data}, + #st{input = #udp{socket = Socket}, output = Out} = S) -> + Out1 = handle_data(Data, Out), + {noreply, S#st{output = Out1}}; +handle_info({plugin, Pid, Data}, #st{input = #plugin{process = Pid}, + output = Out} = S) -> + Out1 = handle_data(Data, Out), + {noreply, S#st{output = Out1}}; +handle_info({tcp_passive, Socket}, #st{input = #tcp{socket = Socket, + active = Active}} = S) -> + inet:setopts(Socket, Active), + {noreply, S}; +handle_info({udp_passive, Socket}, #st{input = #udp{socket = Socket, + active = Active}} = S) -> + inet:setopts(Socket, Active), + {noreply, S}; +handle_info({plugin_passive, Pid}, #st{input = #plugin{process = Pid, + active = Active}} = S) -> + Pid ! {plugin_active, Active}, + {noreply, S}; +handle_info({?MODULE, P, Data}, #st{input = #int{process = Pl}, + output = Out} = S) + when Pl =:= P; Pl =:= undefined -> + Out1 = handle_data(Data, Out), + {noreply, S#st{output = Out1}}; +handle_info(_, S) -> + {noreply, S}. + +%% @private +terminate(_, _) -> + ok. + +%% @private +code_change(_FromVsn, S, _Extra) -> + {ok, S}. + +%% End gen_server callbacks + +info_(ID, I, O) -> + [{id, ID}, + {input, ensure_list(pp(I))}, + {output, ensure_list(pp(O))}]. + +ensure_list(I) when is_tuple(I) -> + [I]; +ensure_list(I) when is_list(I) -> + I. + +%% Copied from git:uwiger/jobs/src/jobs_info.erl +pp(L) when is_list(L) -> + [pp(X) || X <- L]; +pp(X) -> + case '#is_record-'(X) of + true -> + RecName = element(1,X), + {RecName, lists:zip( + '#info-'(RecName,fields), + pp(tl(tuple_to_list(X))))}; + false -> + if is_tuple(X) -> + list_to_tuple(pp(tuple_to_list(X))); + true -> + X + end + end. + +init_input(#tcp{port = Port, + options = Opts} = T) -> + _ = spawn_tcp_acceptor(Port, Opts), + T; +init_input(#udp{port = Port, options = Opts} = U) -> + {ok, Socket} = gen_udp:open(Port, Opts), + U#udp{socket = Socket}; +init_input(#plugin{module = Mod, mod_state = St} = P) -> + case Mod:logger_init_input(St) of + {ok, Pid} when is_pid(Pid) -> + P#plugin{process = Pid} + end; +init_input(#int{} = I) -> + I. + + +spawn_tcp_acceptor(Port, Opts) -> + Parent = self(), + spawn_link(fun() -> + {ok, LSock} = gen_tcp:listen(Port, Opts), + {ok, Socket} = gen_tcp:accept(LSock), + ok = gen_tcp:controlling_process(Socket, Parent), + gen_server:cast(Parent, {socket, Socket}) + end). + +init_output(Out) -> + [init_output_(O) || O <- Out]. + +init_output_(#tty{} = TTY) -> TTY; +init_output_(#int{} = Int) -> Int; +init_output_(#ets{tab = T} = E) -> + Tab = case T of + undefined -> + ets:new(?MODULE, [ordered_set]); + _ -> + T + end, + E#ets{tab = Tab}; +init_output_(#plugin{module = Mod, mod_state = St} = P) -> + {ok, St1} = Mod:logger_init_output(St), + P#plugin{mod_state = St1}. + +check_active(Socket, once) -> + inet:setopts(Socket, [{active, once}]); +check_active(_Socket, _) -> + ok. + +handle_data(Data, Out) -> + handle_data(Data, Out, []). + +handle_data(Data, [H|T], Acc) -> + {Data1, H1} = handle_data_(Data, H), + handle_data(Data1, T, [H1|Acc]); +handle_data(_, [], Acc) -> + lists:reverse(Acc). + +handle_data_(Data, #tty{prefix = Pfx} = Out) -> + io:fwrite(iolist_to_binary([Pfx, Data, $\n])), + {Data, Out}; +handle_data_(Data, #ets{tab = T} = Out) -> + ets:insert(T, {erlang:now(), Data}), + {Data, Out}; +handle_data_(Data, #int{process = P} = Out) -> + try P ! {?MODULE, self(), Data} catch _:_ -> error end, + {Data, Out}; +handle_data_(Data, #plugin{module = Mod, mod_state = ModSt} = Out) -> + {Data1, ModSt1} = Mod:logger_handle_data(Data, ModSt), + {Data1, Out#plugin{mod_state = ModSt1}}. + + + diff --git a/src/exometer_report_logger_sup.erl b/src/exometer_report_logger_sup.erl new file mode 100644 index 0000000..9adf63a --- /dev/null +++ b/src/exometer_report_logger_sup.erl @@ -0,0 +1,26 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +-module(exometer_report_logger_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). + +start_link() -> + supervisor:start_link({local,?MODULE}, ?MODULE, []). + + +init(_) -> + {ok, {{simple_one_for_one, 3, 10}, + [?CHILD(exometer_report_logger, worker)]}}. diff --git a/src/exometer_util.erl b/src/exometer_util.erl index d12d32c..0eee13a 100644 --- a/src/exometer_util.erl +++ b/src/exometer_util.erl @@ -15,6 +15,7 @@ [ timestamp/0, timestamp_to_datetime/1, + get_opt/2, get_opt/3, get_env/2, tables/0, @@ -91,6 +92,13 @@ get_env1(App, Key) -> Other -> Other end. +get_opt(K, Opts) -> + case lists:keyfind(K, 1, Opts) of + {_, V} -> V; + false -> + error({required, K}) + end. + get_opt(K, Opts, Default) -> case lists:keyfind(K, 1, Opts) of {_, V} -> V; diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index da2c5f0..bd8a246 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -120,10 +120,12 @@ init_per_testcase(Case, Config) when Case == test_function_match -> ok = application:set_env(stdlib, exometer_predefined, {script, "../../test/data/test_defaults.script"}), {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), + ct:log("StartedApps = ~p~n", [StartedApps]), [{started_apps, StartedApps} | Config]; init_per_testcase(test_app_predef, Config) -> compile_app1(Config), {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), + ct:log("StartedApps = ~p~n", [StartedApps]), Scr = filename:join(filename:dirname( filename:absname(?config(data_dir, Config))), "data/app1.script"), @@ -131,13 +133,14 @@ init_per_testcase(test_app_predef, Config) -> [{started_apps, StartedApps} | Config]; init_per_testcase(_Case, Config) -> {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), + ct:log("StartedApps = ~p~n", [StartedApps]), [{started_apps, StartedApps} | Config]. end_per_testcase(Case, Config) when Case == test_folsom_histogram; Case == test_history1_folsom; Case == test_history4_folsom -> - [application:stop(App) || App <- ?config(started_apps, Config)], + stop_started_apps(Config), folsom:stop(), application:stop(bear), ok; @@ -145,17 +148,21 @@ end_per_testcase(Case, Config) when Case == test_ext_predef; Case == test_function_match -> ok = application:unset_env(common_test, exometer_predefined), - [application:stop(App) || App <- ?config(started_apps, Config)], ok = application:stop(setup), + stop_started_apps(Config), ok; end_per_testcase(test_app_predef, Config) -> ok = application:stop(app1), - [application:stop(App) || App <- ?config(started_apps, Config)], + stop_started_apps(Config), ok; end_per_testcase(_Case, Config) -> - [application:stop(App) || App <- ?config(started_apps, Config)], + stop_started_apps(Config), ok. +stop_started_apps(Config) -> + [application:stop(App) || + App <- lists:reverse(?config(started_apps, Config))]. + %%%=================================================================== %%% Test Cases %%%=================================================================== diff --git a/test/exometer_report_SUITE.erl b/test/exometer_report_SUITE.erl new file mode 100644 index 0000000..9cf7500 --- /dev/null +++ b/test/exometer_report_SUITE.erl @@ -0,0 +1,192 @@ +-module(exometer_report_SUITE). + +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% common_test exports +-export( + [ + all/0, groups/0, suite/0, + init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2 + ]). + +%% test case exports +-export( + [ + test_newentry/1, + test_subscribe/1, + test_subscribe_find/1, + test_subscribe_select/1 + ]). + +-behaviour(exometer_report_logger). +-export([logger_init_output/1, + logger_handle_data/2]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("exometer_core/include/exometer.hrl"). + +-define(DEFAULT_PORT, 8888). + +all() -> + [ + {group, test_reporter} + ]. + +groups() -> + [ + {test_reporter, [shuffle], + [ + test_newentry, + test_subscribe, + test_subscribe_find, + test_subscribe_select + ]} + ]. + +suite() -> + []. + +init_per_suite(Config) -> + Config. + +init_per_testcase(_Case, Config) -> + {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core), + [{started_apps, StartedApps} | Config]. + +end_per_testcase(_Case, Config) -> + stop_started_apps(Config). + +end_per_suite(_Config) -> + ok. + +stop_started_apps(Config) -> + [application:stop(App) || + App <- lists:reverse(?config(started_apps, Config))]. + +test_newentry(Config) -> + {ok, Info} = start_logger_and_reporter(test_udp, Config), + Tab = ets_tab(Info), + [] = ets:tab2list(Tab), + exometer:new([c], counter, []), + %% exometer_report:trigger_interval(test_udp, main), + R1 = check_logger_msg(), + [{_,{newentry,#exometer_entry{name = [c], type = counter}} = R1}] = + ets:tab2list(Tab), + ok. + +test_subscribe(Config) -> + ok = exometer:new([c], counter, []), + {ok, _Info} = start_logger_and_reporter(test_udp, Config), + exometer_report:subscribe(test_udp, [c], value, main, true), + %% exometer_report:trigger_interval(test_udp, main), + {subscribe, [{metric, [c]}, + {datapoint, value} | _]} = check_logger_msg(), + ok. + +test_subscribe_find(Config) -> + ok = exometer:new([c,1], counter, []), + ok = exometer:new([c,2], counter, []), + {ok, Info} = start_logger_and_reporter(test_udp, Config), + exometer_report:subscribe(test_udp, {find,[c,'_']}, value, main, true), + {subscribe, [{metric, {find,[c,'_']}}, + {datapoint, value} | _]} = R1 = check_logger_msg(), + exometer_report:trigger_interval(test_udp, main), + {report, [{prefix,[]},{metric,[c,1]}|_]} = R2 = check_logger_msg(), + {report, [{prefix,[]},{metric,[c,2]}|_]} = R3 = check_logger_msg(), + Tab = ets_tab(Info), + [{_,R1},{_,R2},{_,R3}] = ets:tab2list(Tab), + ok. + +test_subscribe_select(Config) -> + ok = exometer:new([c,1], counter, []), + ok = exometer:new([c,2], counter, []), + ok = exometer:new([c,3], counter, []), + {ok, Info} = start_logger_and_reporter(test_udp, Config), + exometer_report:subscribe( + test_udp, + {select,[{ {[c,'$1'],'_','_'},[{'<','$1',3}], ['$_'] }]}, + value, main, true), + {subscribe, [{metric, {select,_}}, + {datapoint, value} | _]} = R1 = check_logger_msg(), + exometer_report:trigger_interval(test_udp, main), + {report, [{prefix,[]},{metric,[c,1]}|_]} = R2 = check_logger_msg(), + {report, [{prefix,[]},{metric,[c,2]}|_]} = R3 = check_logger_msg(), + Tab = ets_tab(Info), + [{_,R1},{_,R2},{_,R3}] = ets:tab2list(Tab), + ok. + + +start_logger_and_reporter(Reporter, Config) -> + Port = get_port(Config), + Res = exometer_report_logger:new( + [{id, ?MODULE}, + {input, [{mode, plugin}, + {module, exometer_test_udp_reporter}, + {state, {Port, []}}]}, + {output, [{mode, plugin}, + {module, exometer_test_udp_reporter}]}, + {output, [{mode, ets}]}, + {output, [{mode, plugin}, + {module, ?MODULE}, + {state, self()}]}]), + ct:log("Logger start: ~p~n", [Res]), + Info = exometer_report_logger:info(), + ct:log("Logger Info = ~p~n", [Info]), + ok = exometer_report:add_reporter( + Reporter, + [{module, exometer_test_udp_reporter}, + {hostname, "localhost"}, + {port, Port}, + {intervals, [{main, manual}]}]), + {ok, Info}. + +check_logger_msg() -> + receive + {logger_got, Data} -> + ct:log("logger_got: ~p~n", [Data]), + Data + after 1000 -> + error(logger_ack_timeout) + end. + +ets_tab(Info) -> + [T] = [tree_opt([output,ets,tab], I) || {_,[{id,?MODULE}|I]} <- Info], + T. + +get_port(Config) -> + case ?config(port, Config) of + undefined -> + ?DEFAULT_PORT; + Port -> + Port + end. + +tree_opt([H|T], L) when is_list(L) -> + case lists:keyfind(H, 1, L) of + {_, Val} -> + case T of + [] -> Val; + [_|_] -> + tree_opt(T, Val) + end; + false -> + undefined + end; +tree_opt([], _) -> + undefined. + + +logger_init_output(Pid) -> + {ok, Pid}. + +logger_handle_data(Data, Pid) -> + Pid ! {logger_got, Data}, + {Data, Pid}. diff --git a/test/exometer_test_udp_reporter.erl b/test/exometer_test_udp_reporter.erl new file mode 100644 index 0000000..2cbe421 --- /dev/null +++ b/test/exometer_test_udp_reporter.erl @@ -0,0 +1,118 @@ +-module(exometer_test_udp_reporter). + +-behaviour(exometer_report). +-export( + [ + exometer_init/1, + exometer_info/2, + exometer_cast/2, + exometer_call/3, + exometer_report/5, + exometer_subscribe/5, + exometer_unsubscribe/4, + exometer_newentry/2, + exometer_setopts/4, + exometer_terminate/2 + ]). + +-behaviour(exometer_report_logger). +-export([ + logger_init_input/1, + logger_init_output/1, + logger_handle_data/2 + ]). + +-import(exometer_util, [get_opt/2, get_opt/3]). + +-include_lib("kernel/include/inet.hrl"). +-record(st, {socket, address, port, type_map, prefix = []}). + +-define(DEFAULT_HOST, "localhost"). + +exometer_init(Opts) -> + Port = get_opt(port, Opts), + {ok, Host} = inet:gethostbyname(get_opt(hostname, Opts, ?DEFAULT_HOST)), + [Addr|_] = Host#hostent.h_addr_list, + AddrType = Host#hostent.h_addrtype, + TypeMap = get_opt(type_map, Opts, []), + Prefix = get_opt(prefix, Opts, []), + case gen_udp:open(0, [AddrType]) of + {ok, Socket} -> + {ok, #st{socket = Socket, address = Addr, port = Port, + type_map = TypeMap, prefix = Prefix}}; + {error, _} = Error -> + Error + end. + +exometer_report(Metric, DataPoint, Extra, Value, #st{type_map = TypeMap, + prefix = Pfx} = St) -> + RptType = exometer_util:report_type({Metric,DataPoint}, Extra, TypeMap), + ok = send({report, [{prefix, Pfx}, + {metric, Metric}, + {datapoint, DataPoint}, + {extra, Extra}, + {report_type, RptType}, + {value, Value}]}, St), + {ok, St}. + +exometer_subscribe(Metric, DataPoint, Extra, Interval, St) -> + ok = send({subscribe, [{metric, Metric}, + {datapoint, DataPoint}, + {extra, Extra}, + {interval, Interval}]}, St), + {ok, St}. + +exometer_unsubscribe(Metric, DataPoint, Extra, St) -> + ok = send({unsubscribe, [{metric, Metric}, + {datapoint, DataPoint}, + {extra, Extra}]}, St), + {ok, St}. + +exometer_call(Req, From, St) -> + ok = send({call, Req, From}, St), + {reply, {test_reply, ok}, St}. + +exometer_cast(Cast, St) -> + ok = send({cast, Cast}, St), + {ok, St}. + +exometer_info(I, St) -> + ok = send({info, I}, St), + {ok, St}. + +exometer_newentry(E, St) -> + ok = send({newentry, E}, St), + {ok, St}. + +exometer_setopts(Metric, Opts, Status, St) -> + ok = send({setopts, [{metric, Metric}, + {options, Opts}, + {status, Status}]}, St), + {ok, St}. + +exometer_terminate(_, _) -> + ok. + +send(Term, #st{socket = Socket, address = Address, port = Port}) -> + gen_udp:send(Socket, Address, Port, term_to_binary(Term, [compressed])). + + +logger_init_input({Port, Opts}) -> + Parent = self(), + {ok, spawn_link(fun() -> + {ok, Socket} = gen_udp:open(Port, [binary|Opts]), + input_loop(Socket, Parent) + end)}. + +logger_init_output(_) -> + {ok, []}. + +logger_handle_data(Data, St) -> + {binary_to_term(iolist_to_binary([Data])), St}. + +input_loop(Socket, Parent) -> + receive + {udp, Socket, _Host, _Port, Data} -> + Parent ! {plugin, self(), Data} + end, + input_loop(Socket, Parent). From d13908893360983dcf7c5e22dc08a3338957e688 Mon Sep 17 00:00:00 2001 From: Tino Breddin Date: Tue, 7 Apr 2015 11:58:17 +0200 Subject: [PATCH 26/40] add value16, value32, value64 datapoints to counter --- src/exometer.erl | 13 +++++++++++-- test/exometer_SUITE.erl | 23 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/exometer.erl b/src/exometer.erl index 090c16b..0fbd0fb 100644 --- a/src/exometer.erl +++ b/src/exometer.erl @@ -1056,13 +1056,22 @@ type_arg_first(Opts) -> %% Retrieve individual data points for the counter maintained by %% the exometer record itself. get_ctr_datapoint(#exometer_entry{name = Name}, value) -> - {value, lists:sum([ets:lookup_element(T, Name, #exometer_entry.value) - || T <- exometer_util:tables()])}; + {value, counter_sum(Name)}; +get_ctr_datapoint(#exometer_entry{name = Name}, value16) -> + {value16, counter_sum(Name) rem 16#FFFF}; +get_ctr_datapoint(#exometer_entry{name = Name}, value32) -> + {value32, counter_sum(Name) rem 16#FFFFFFFF}; +get_ctr_datapoint(#exometer_entry{name = Name}, value64) -> + {value64, counter_sum(Name) rem 16#FFFFFFFFFFFFFFFF}; get_ctr_datapoint(#exometer_entry{timestamp = TS}, ms_since_reset) -> {ms_since_reset, exometer_util:timestamp() - TS}; get_ctr_datapoint(#exometer_entry{}, Undefined) -> {Undefined, undefined}. +counter_sum(Name) -> + lists:sum([ets:lookup_element(T, Name, #exometer_entry.value) + || T <- exometer_util:tables()]). + get_gauge_datapoint(#exometer_entry{value = Value}, value) -> {value, Value}; get_gauge_datapoint(#exometer_entry{timestamp = TS}, ms_since_reset) -> diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index da2c5f0..f75011d 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -23,6 +23,7 @@ test_std_counter/1, test_gauge/1, test_fast_counter/1, + test_wrapping_counter/1, test_update_or_create/1, test_update_or_create2/1, test_default_override/1, @@ -68,7 +69,8 @@ groups() -> [ test_std_counter, test_gauge, - test_fast_counter + test_fast_counter, + test_wrapping_counter ]}, {test_defaults, [shuffle], [ @@ -191,6 +193,25 @@ test_fast_counter(_Config) -> {ok, [{value, 2}, {ms_since_reset, _}]} = exometer:get_value(C), ok. +test_wrapping_counter(_Config) -> + C = [?MODULE, ctr, ?LINE], + ok = exometer:new(C, counter, []), + Max16 = 65534, + Max32 = 4294967294, + Max64 = 18446744073709551614, + Max64p1 = 18446744073709551615, + Max64p21 = 18446744073709551635, + ok = exometer:update(C, Max64), + {ok, [{value, Max64}, {value16, Max16}, {value32, Max32}, {value64, Max64}]} = + exometer:get_value(C, [value, value16, value32, value64]), + ok = exometer:update(C, 1), + {ok, [{value, Max64p1}, {value16, 0}, {value32, 0}, {value64, 0}]} = + exometer:get_value(C, [value, value16, value32, value64]), + [ok = exometer:update(C, 1) || _ <- lists:seq(1, 20)], + {ok, [{value, Max64p21}, {value16, 20}, {value32, 20}, {value64, 20}]} = + exometer:get_value(C, [value, value16, value32, value64]), + ok. + test_update_or_create(_Config) -> {error, not_found} = exometer:update([a,b,c], 2), {error, no_template} = exometer:update_or_create([a,b,c], 10), From cc08af865807a47ecc0288e7f0dd539ac4f8c18f Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Tue, 7 Apr 2015 15:27:54 +0200 Subject: [PATCH 27/40] fix copyright notices --- src/exometer_core_sup.erl | 2 +- src/exometer_report_logger.erl | 2 +- src/exometer_report_logger_sup.erl | 2 +- test/exometer_report_SUITE.erl | 6 +++--- test/exometer_test_udp_reporter.erl | 9 +++++++++ 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/exometer_core_sup.erl b/src/exometer_core_sup.erl index 5608a7f..4405c2c 100644 --- a/src/exometer_core_sup.erl +++ b/src/exometer_core_sup.erl @@ -1,6 +1,6 @@ %% ------------------------------------------------------------------- %% -%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2014-15 Basho Technologies, Inc. All Rights Reserved. %% %% This Source Code Form is subject to the terms of the Mozilla Public %% License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/src/exometer_report_logger.erl b/src/exometer_report_logger.erl index bb7d729..279f0ce 100644 --- a/src/exometer_report_logger.erl +++ b/src/exometer_report_logger.erl @@ -1,6 +1,6 @@ %% ------------------------------------------------------------------- %% -%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved. %% %% This Source Code Form is subject to the terms of the Mozilla Public %% License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/src/exometer_report_logger_sup.erl b/src/exometer_report_logger_sup.erl index 9adf63a..f86c073 100644 --- a/src/exometer_report_logger_sup.erl +++ b/src/exometer_report_logger_sup.erl @@ -1,6 +1,6 @@ %% ------------------------------------------------------------------- %% -%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved. %% %% This Source Code Form is subject to the terms of the Mozilla Public %% License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/test/exometer_report_SUITE.erl b/test/exometer_report_SUITE.erl index 9cf7500..17131a6 100644 --- a/test/exometer_report_SUITE.erl +++ b/test/exometer_report_SUITE.erl @@ -1,14 +1,14 @@ --module(exometer_report_SUITE). - %% ------------------------------------------------------------------- %% -%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved. %% %% This Source Code Form is subject to the terms of the Mozilla Public %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at http://mozilla.org/MPL/2.0/. %% %% ------------------------------------------------------------------- +-module(exometer_report_SUITE). + %% common_test exports -export( [ diff --git a/test/exometer_test_udp_reporter.erl b/test/exometer_test_udp_reporter.erl index 2cbe421..3cd6504 100644 --- a/test/exometer_test_udp_reporter.erl +++ b/test/exometer_test_udp_reporter.erl @@ -1,3 +1,12 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- -module(exometer_test_udp_reporter). -behaviour(exometer_report). From db28de5695dac95c95add5de917a2c50def99ccf Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Thu, 9 Apr 2015 21:48:38 +0200 Subject: [PATCH 28/40] Repaired and extended probe supervision - Delete of failed probe didn't work - Restart parameterization support in exometer_probe (see edoc) - Disabled probes (e.g. histograms) are now terminated - When re-enabled, probes are re-instantitated - If new entries include aliases, these are checked in advance - Support for deleting all cached datapoints for a given entry - Fix exometer:start(), which had become broken - Make sure eunit tests succeed - Add ct.config with lager log config - Fix breakage in CT test case init and cleanup - Add test suite for supervision functionality --- rebar.config | 2 + src/exometer.erl | 40 +++++-- src/exometer_admin.erl | 121 +++++++++++++++++---- src/exometer_alias.erl | 1 + src/exometer_cache.erl | 11 +- src/exometer_probe.erl | 198 +++++++++++++++++++++++++++++++++- src/exometer_util.erl | 45 +++++++- test/ct.config | 8 ++ test/exometer_SUITE.erl | 17 +-- test/exometer_error_SUITE.erl | 155 ++++++++++++++++++++++++++ 10 files changed, 560 insertions(+), 38 deletions(-) create mode 100644 test/ct.config create mode 100644 test/exometer_error_SUITE.erl diff --git a/rebar.config b/rebar.config index 8332680..76d5d2f 100644 --- a/rebar.config +++ b/rebar.config @@ -36,6 +36,8 @@ "https://github.com/Feuerlabs/exometer_core", "master"}} ]}. +{ct_extra_params, "-config test/ct.config"}. + {xref_checks, [ undefined_function_calls, diff --git a/src/exometer.erl b/src/exometer.erl index 0fbd0fb..32706fe 100644 --- a/src/exometer.erl +++ b/src/exometer.erl @@ -92,8 +92,8 @@ %% @doc Start exometer and dependent apps (for testing). start() -> - lager:start(), - application:start(exometer_core). + {ok,_} = exometer_util:ensure_all_started(exometer_core), + ok. %% @doc Stop exometer and dependent apps (for testing). stop() -> @@ -550,9 +550,28 @@ setopts(Name, Options) when is_list(Name), is_list(Options) -> {error, not_found} end. -module_setopts(#exometer_entry{behaviour = probe}=E, Options, NewStatus) -> +module_setopts(#exometer_entry{behaviour = probe, + name = Name, + status = St0} = E, Options, NewStatus) -> reporter_setopts(E, Options, NewStatus), - exometer_probe:setopts(E, Options, NewStatus); + case NewStatus of + disabled when ?IS_ENABLED(St0) -> + exometer_cache:delete_name(Name), + exometer_probe:stop_probe(E), + update_entry_elems(Name, [{#exometer_entry.ref, undefined}]); + enabled when ?IS_DISABLED(St0) -> + %% Previously, probes were not stopped when disabled + OldRef = E#exometer_entry.ref, + case is_pid(OldRef) andalso is_process_alive(OldRef) of + true -> ok; + false -> + {ok, Ref} = exometer_probe:start_probe(E), + update_entry_elems(Name, [{#exometer_entry.ref, Ref}]) + end; + false -> + exometer_probe:setopts(E, Options, NewStatus) + end, + ok; module_setopts(#exometer_entry{behaviour = entry, module=M} = E, Options, NewStatus) -> @@ -565,8 +584,8 @@ module_setopts(#exometer_entry{behaviour = entry, ok -> reporter_setopts(E, Options, NewStatus), ok; - E -> - E + {error,_} = Error -> + Error end end. @@ -1129,11 +1148,18 @@ create_entry(#exometer_entry{module = exometer, create_entry(#exometer_entry{module = Module, type = Type, name = Name, + status = Status, options = Opts} = E) -> case case Module:behaviour() of probe -> - {probe, exometer_probe:new(Name, Type, [{ arg, Module} | Opts ]) }; + if ?IS_ENABLED(Status) -> + {probe, exometer_probe:new( + Name, Type, [{ arg, Module} | Opts ])}; + ?IS_DISABLED(Status) -> + %% Don't start probe if disabled + {probe, ok} + end; entry -> {entry, Module:new(Name, Type, Opts) }; diff --git a/src/exometer_admin.erl b/src/exometer_admin.erl index 4d55892..c4d0a10 100644 --- a/src/exometer_admin.erl +++ b/src/exometer_admin.erl @@ -45,8 +45,9 @@ ]). -export([find_auto_template/1, prefixes/1, make_patterns/2]). --export([monitor/2, demonitor/1]). +-export([monitor/2, monitor/3, demonitor/1]). +-compile({no_auto_import, [monitor/3]}). -include("exometer.hrl"). -include("log.hrl"). @@ -228,7 +229,10 @@ check_type_arg(Type, Opts) -> {Type, Opts}. monitor(Name, Pid) when is_pid(Pid) -> - gen_server:cast(?MODULE, {monitor, Name, Pid}). + monitor(Name, Pid, delete). + +monitor(Name, Pid, OnError) when is_pid(Pid) -> + gen_server:cast(?MODULE, {monitor, Name, Pid, OnError}). demonitor(Pid) when is_pid(Pid) -> gen_server:cast(?MODULE, {demonitor, Pid}). @@ -261,7 +265,7 @@ start_link() -> init(_) -> {ok, #st{}}. -handle_call({new_entry, Name, Type, Opts, AllowExisting}, _From, S) -> +handle_call({new_entry, Name, Type, Opts, AllowExisting} = _Req, _From, S) -> try #exometer_entry{options = NewOpts} = E0 = lookup_definition(Name, Type, Opts), @@ -269,14 +273,24 @@ handle_call({new_entry, Name, Type, Opts, AllowExisting}, _From, S) -> case {ets:member(exometer_util:table(), Name), AllowExisting} of {true, false} -> {reply, {error, exists}, S}; - _ -> + _Other -> + lager:debug("_Other = ~p~n", [_Other]), E1 = process_opts(E0, NewOpts), - Res = exometer:create_entry(E1), - exometer_report:new_entry(E1), + Res = try exometer:create_entry(E1), + exometer_report:new_entry(E1) + catch + error:Error1 -> + lager:debug( + "ERROR create_entry(~p) :- ~p~n~p", + [E1, Error1, erlang:get_stacktrace()]), + erlang:error(Error1) + end, {reply, Res, S} end catch error:Error -> + lager:error("~p -*-> error:~p~n~p~n", + [_Req, Error, erlang:get_stacktrace()]), {reply, {error, Error}, S} end; handle_call({repair_entry, Name}, _From, S) -> @@ -328,9 +342,9 @@ handle_call({auto_create, Name}, _From, S) -> handle_call(_, _, S) -> {reply, error, S}. -handle_cast({monitor, Name, Pid}, S) -> +handle_cast({monitor, Name, Pid, OnError}, S) -> Ref = erlang:monitor(process, Pid), - put(Ref, Name), + put(Ref, {Name, OnError}), put(Pid, Ref), {noreply, S}; handle_cast({demonitor, Pid}, S) -> @@ -350,14 +364,21 @@ handle_info({'DOWN', Ref, _, Pid, _}, S) -> case get(Ref) of undefined -> {noreply, S}; - Proc when is_atom(Proc) -> + {Name, OnError} when is_atom(Name); is_list(Name) -> + erase(Ref), + erase(Pid), + on_error(Name, OnError), + {noreply, S}; + Name when is_atom(Name) -> + %% BW compat erase(Ref), erase(Pid), {noreply, S}; Name when is_list(Name) -> + %% BW compat erase(Ref), erase(Pid), - try delete_entry_(Name) catch error:_ -> ok end, + on_error(Name, delete), {noreply, S} end; handle_info(_, S) -> @@ -404,7 +425,47 @@ tables() -> %% ==== -lookup_definition(Name, ad_hoc, Opts) -> +on_error(Name, {restart, {M, F, A}}) -> + try call_restart(M, F, A) of + {ok, Ref} -> + if is_list(Name) -> + [ets:update_element(T, Name, [{#exometer_entry.ref, Ref}]) + || T <- exometer_util:tables()]; + true -> ok + end, + ok; + disable -> + try_disable_entry_(Name); + delete -> + try_delete_entry_(Name); + Other -> + restart_failed(Name, Other) + catch error:R -> + restart_failed(Name, {error, R}) + end, + ok; +on_error(Name, delete) -> + try_delete_entry_(Name); +on_error(_Proc, _OnError) -> + %% Not good, but will do for now. + lager:debug("Unrecognized OnError: ~p (~p)~n", [_OnError, _Proc]), + ok. + +call_restart(M, F, A) -> + apply(M, F, A). + +restart_failed(Name, Error) -> + lager:debug("Restart failed ~p: ~p~n", [Name, Error]), + if is_list(Name) -> + try_delete_entry_(Name); + true -> + ok + end. + +lookup_definition(Name, Type, Opts) -> + check_aliases(lookup_definition_(Name, Type, Opts)). + +lookup_definition_(Name, ad_hoc, Opts) -> case [K || K <- [module, type], not lists:keymember(K, 1, Opts)] of [] -> {E0, Opts1} = @@ -426,7 +487,7 @@ lookup_definition(Name, ad_hoc, Opts) -> [_|_] = Missing -> error({required, Missing}) end; -lookup_definition(Name, Type, Opts) -> +lookup_definition_(Name, Type, Opts) -> E = case ets:prev(?EXOMETER_SHARED, {default, Type, <<>>}) of {default, Type, N} = D0 when N==[''], N==Name -> case ets:lookup(?EXOMETER_SHARED, D0) of @@ -460,6 +521,15 @@ merge_opts(Opts, #exometer_entry{options = DefOpts} = E) -> end, DefOpts, Opts), E#exometer_entry{options = Opts1}. +check_aliases(#exometer_entry{name = N, options = Opts} = E) -> + case lists:keyfind(aliases, 1, Opts) of + {_, Aliases} -> + exometer_alias:check_map([{N, Aliases}]); + _ -> + ok + end, + E. + default_definition_(Name, Type) -> case search_default(Name, Type) of #exometer_entry{} = E -> @@ -600,7 +670,26 @@ process_opts(Entry, Options) -> Entry1 end, Entry#exometer_entry{options = Options}, Options). +try_disable_entry_(Name) when is_list(Name) -> + try exometer:setopts(Name, [{status, disabled}]) + catch + error:Err -> + lager:debug("Couldn't disable ~p: ~p~n", [Name, Err]), + try_delete_entry_(Name) + end; +try_disable_entry_(_Name) -> + ok. + +try_delete_entry_(Name) -> + try delete_entry_(Name) + catch + error:R -> + lager:debug("Couldn't delete ~p: ~p~n", [Name, R]), + ok + end. + delete_entry_(Name) -> + exometer_cache:delete_name(Name), case ets:lookup(exometer_util:table(), Name) of [#exometer_entry{module = exometer, type = Type}] when Type==counter; Type==gauge -> @@ -617,9 +706,7 @@ delete_entry_(Name) -> end, ok; [#exometer_entry{behaviour = probe, - type = Type, ref = Ref} = E] -> - [ exometer_cache:delete(Name, DataPoint) || - DataPoint <- exometer_util:get_datapoints(E)], + type = Type, ref = Ref}] -> try exometer_probe:delete(Name, Type, Ref) after @@ -628,9 +715,7 @@ delete_entry_(Name) -> end, ok; [#exometer_entry{module= Mod, behaviour = entry, - type = Type, ref = Ref} = E] -> - [ exometer_cache:delete(Name, DataPoint) || - DataPoint <- exometer_util:get_datapoints(E)], + type = Type, ref = Ref}] -> try Mod:delete(Name, Type, Ref) after [ets:delete(T, Name) || diff --git a/src/exometer_alias.erl b/src/exometer_alias.erl index 20b7dd5..bb15071 100644 --- a/src/exometer_alias.erl +++ b/src/exometer_alias.erl @@ -25,6 +25,7 @@ -export([new/3, load/1, unload/1, + check_map/1, delete/1, update/2, resolve/1, diff --git a/src/exometer_cache.erl b/src/exometer_cache.erl index eacc53a..620e8a2 100644 --- a/src/exometer_cache.erl +++ b/src/exometer_cache.erl @@ -16,7 +16,8 @@ -export([read/2, %% (Name, DataPoint) -> {ok, Value} | not_found write/3, %% (Name, DataPoint, Value) -> ok write/4, %% (Name, DataPoint, Value, TTL) -> ok - delete/2 + delete/2, + delete_name/1 ]). -export([init/1, @@ -63,6 +64,14 @@ delete(Name, DataPoint) -> %% Cancel the timer? ets:delete(?TABLE, path(Name, DataPoint)). +delete_name(Name) -> + %% This function currently doesn't cancel any timers. The cost of doing + %% so would outweigh the cost of reacting to any timers that happen to + %% fire (timer-based cleanup matches on TRef, so will not accidentally + %% delete the wrong entries.) + ets:match_delete(?TABLE, #cache{name = {Name,'_'}, _ = '_'}), + ok. + start_timer(Name, TTL, TS) -> gen_server:cast(?MODULE, {start_timer, Name, TTL, TS}). diff --git a/src/exometer_probe.erl b/src/exometer_probe.erl index bb19268..2cfeb67 100644 --- a/src/exometer_probe.erl +++ b/src/exometer_probe.erl @@ -376,12 +376,36 @@ %% NewState}', where `NewState' contains the new state of the probe %% that reflects the processed message. %% - +%% == Fault tolerance == +%% Probes are supervised by the `exometer_admin' process, and can be restarted +%% after a crash. Restart parameters are provided via the option +%% `{restart, Params}', where `Params' is a list of `{Frequency, Action}' +%% tuples. `Frequency' is either `{Count, MilliSecs}' or ``'_''', and +%% the corresponding `Action :: restart | disable | delete' will be performed +%% if the frequency of restarts falls within the given limit. +%% +%% For example, `[{{3, 1000}, restart}]' will allow 3 restarts within a 1-second +%% window. The matching is performed from top to bottom, and the first matching +%% pattern is acted upon. If no matching pattern is found, the default action +%% is `delete'. A pattern ``{'_', Action}'' acts as a catch-all, and should +%% be put last in the list. +%% +%% It is also possible to specify ``{{Count, '_'}, Action}'', which means +%% that a total of `Count' restarts is permitted, regardless of how far apart +%% they are. The count is reset after each restart. +%% +%% Example: +%%
+%%   {restart, [{{3,1000}, restart},   % up to 3 restarts within 1 sec
+%%              {{4,'_'} , disable},   % a total of up to 4 restarts
+%%              {'_'     , delete}]}   % anything else
+%% 
+%% @end -module(exometer_probe). -behaviour(exometer_entry). - % exometer_entry callb +%% exometer_entry callbacks -export( [ behaviour/0, @@ -395,8 +419,17 @@ setopts/3 ]). +-export([start_probe/1, + stop_probe/1]). + +-export([restart/4]). + -include("exometer.hrl"). +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + -record(st, { name, type, @@ -437,18 +470,115 @@ -callback probe_code_change(any(), mod_state(), any()) -> {ok, mod_state()}. new(Name, Type, [{arg, Module}|Opts]) -> + Restart = proplists:get_value(restart, Opts, default_restart()), + SpawnOpts = proplists:get_value(spawn_opts, Opts, spawn_opts()), { ok, exometer_proc:spawn_process( Name, fun() -> init(Name, Type, Module, Opts) - end) + end, proc_opts(Name, Module, Restart, SpawnOpts)) }; - new(Name, Type, Options) -> %% Extract the module to use. {value, { module, Module }, Opts1 } = lists:keytake(module, 1, Options), new(Name, Type, [{arg, Module} | Opts1]). +proc_opts(Name, Module, Restart, SpawnOpts) when is_list(Restart) -> + proc_opts_(Name, Module, on_error_init(Restart), SpawnOpts); +proc_opts(Name, Module, Restart, SpawnOpts) -> + proc_opts_(Name, Module, Restart, SpawnOpts). + +proc_opts_(Name, Module, Restart, SpawnOpts) -> + OnError = on_error(Name, Module, Restart, SpawnOpts), + [{on_error, OnError}, {spawn_opts, SpawnOpts}]. + + +spawn_opts() -> + [{fullsweep_after, 10}]. + +on_error_init(R) when is_list(R) -> + {0, [], max_time(R), R}. + +on_error(Name, Module, R, SpawnOpts) -> + {restart, {?MODULE, restart, [Name, Module, R, SpawnOpts]}}. + +check_restart(Restart) -> + TS = exometer_util:timestamp(), + check_restart(TS, Restart). + +check_restart(TS, {Total, Hist, MaxT, R}) when is_integer(MaxT) -> + NewTotal = Total + 1, + Oldest = TS - MaxT, + NewHist = [TS | [T || T <- Hist, + T > Oldest]], + {action(NewTotal, NewHist, MaxT, R), {NewTotal, NewHist, MaxT, R}}; +check_restart(_TS, {Total, Hist, '_', R}) -> + NewTotal = Total + 1, + {action(NewTotal, [], '_', R), {NewTotal, Hist, '_', R}}. + + +max_time([{{_,T},_}|Rest]) -> + max_time(Rest, T); +max_time([{_,_}|Rest]) -> + max_time(Rest); +max_time([]) -> + '_'. + +max_time([{{_,T},_}|Rest], Max) when is_integer(T) -> + max_time(Rest, erlang:max(T, Max)); +max_time([_|Rest], Max) -> + max_time(Rest, Max); +max_time([], Max) -> + Max. + +action(_Total, _Hist, _MaxT, [{'_', Action}|_]) -> + Action; +action(Total, Hist, MaxT, [{{N,'_'}, Action}|R]) -> + if N >= Total -> Action; + true -> action(Total, Hist, MaxT, R) + end; +action(Total, Hist, MaxT, [{{N,T}, Action}|R]) -> + case within(Hist, N, T) of + true -> Action; + false -> + action(Total, Hist, MaxT, R) + end; +action(_, _, _, []) -> + delete. + +within([T|Ts], N, Tm) -> + within(Ts, 1, N, T - Tm); +within([], _, _) -> + false. + +within([H|T], N, M, Lim) when H > Lim -> + N1 = N + 1, + if N1 =< M -> + within(T, N1, M, Lim); + true -> + false + end; +within(_, _, _, _) -> + true. + +restart(Name, Module, Error, SpawnOpts) -> + Type = exometer:info(Name, type), + Opts = exometer:info(Name, options), + case check_restart(Error) of + {restart, Error1} -> + {ok, exometer_proc:spawn_process( + Name, fun() -> + init(Name, Type, Module, Opts) + end, + proc_opts(Name, Module, Error1, SpawnOpts))}; + {Other, _} when Other==delete; Other==disable -> + Other + end. + +default_restart() -> + [{{3, 60000}, restart}, + {'_', disable}]. + %% Should never be called directly for exometer_probe. behaviour() -> entry. @@ -478,6 +608,37 @@ reset(_Name, _Type, Pid) when is_pid(Pid) -> sample(_Name, _Type, Pid) when is_pid(Pid) -> exometer_proc:cast(Pid, sample). + +%% === housekeeping functions used e.g. at enable/disable + +%% @private +stop_probe(#exometer_entry{name = Name, + type = Type, + ref = Ref}) -> + exometer_cache:delete_name(Name), + if is_pid(Ref) -> + try exometer_probe:delete(Name, Type, Ref) + catch + error:_ -> + kill_probe(Ref) + end; + true -> + ok + end. + +kill_probe(Ref) when is_pid(Ref) -> + exometer_admin:demonitor(Ref), + exit(Ref, kill). + +%% @private +start_probe(#exometer_entry{module = Module, + name = Name, + type = Type, + options = Opts}) -> + new(Name, Type, [{ arg, Module} | Opts ]). + +%% == Probe implementation + init(Name, Type, Mod, Opts) -> process_flag(min_heap_size, 40000), {St0, Opts1} = process_opts(Opts, #st{name = Name, @@ -632,3 +793,32 @@ process_opts([Opt|T], St, Acc) -> process_opts(T, St, [Opt | Acc]); process_opts([], St, Acc) -> {St, lists:reverse(Acc)}. + +%% EUnit + +-ifdef(TEST). + +restart_test_() -> + [ + ?_test(t_restart_1()), + ?_test(t_restart_2()) + ]. + +t_restart_1() -> + R = [{{3,1000}, restart}, % up to 3 restarts within 1 sec + {{4,'_'} , disable}, % a total of up to 4 restarts + {'_', delete}], % anything else + OE = on_error_init(R), + {restart, OE1} = check_restart(100, OE), + {restart, OE2} = check_restart(200, OE1), + {restart, OE3} = check_restart(300, OE2), + {disable, OE4} = check_restart(400, OE3), + {delete , _} = check_restart(500, OE4), + ok. + +t_restart_2() -> + R = [], + {delete, _} = check_restart({1, [500], 1000, R}), + ok. + +-endif. diff --git a/src/exometer_util.erl b/src/exometer_util.erl index d12d32c..37269f9 100644 --- a/src/exometer_util.erl +++ b/src/exometer_util.erl @@ -15,6 +15,7 @@ [ timestamp/0, timestamp_to_datetime/1, + get_opt/2, get_opt/3, get_env/2, tables/0, @@ -33,7 +34,8 @@ set_status/2, set_event_flag/2, clear_event_flag/2, - test_event_flag/2 + test_event_flag/2, + ensure_all_started/1 ]). -export_type([timestamp/0]). @@ -91,6 +93,13 @@ get_env1(App, Key) -> Other -> Other end. +get_opt(K, Opts) -> + case lists:keyfind(K, 1, Opts) of + {_, V} -> V; + false -> + error({required, K}) + end. + get_opt(K, Opts, Default) -> case lists:keyfind(K, 1, Opts) of {_, V} -> V; @@ -353,6 +362,38 @@ clear_event_flag(update, disabled) -> 0. test_event_flag(update, St) when St band 2#10 =:= 2#10 -> true; test_event_flag(update, _) -> false. +%% This implementation is originally from Basho's Webmachine. On +%% older versions of Erlang, we don't have +%% application:ensure_all_started, so we use this wrapper function to +%% either use the native implementation or our own version, depending +%% on what's available. +-spec ensure_all_started(atom()) -> {ok, [atom()]} | {error, term()}. +ensure_all_started(App) -> + case erlang:function_exported(application, ensure_all_started, 1) of + true -> + application:ensure_all_started(App); + false -> + ensure_all_started(App, []) + end. + +%% This implementation is originally from Basho's +%% Webmachine. Reimplementation of ensure_all_started. NOTE this does +%% not behave the same as the native version in all cases, but as a +%% quick hack it works well enough for our purposes. Eventually I +%% assume we'll drop support for older versions of Erlang and this can +%% be eliminated. +ensure_all_started(App, Apps0) -> + case application:start(App) of + ok -> + {ok, lists:reverse([App | Apps0])}; + {error,{already_started,App}} -> + {ok, lists:reverse(Apps0)}; + {error,{not_started,BaseApp}} -> + {ok, Apps} = ensure_all_started(BaseApp, Apps0), + ensure_all_started(App, [BaseApp|Apps]) + end. + + %% EUnit tests -ifdef(TEST). @@ -364,7 +405,7 @@ key_match_test() -> {ok,yes} = report_type([a,b,c], [], [{[a,b,'_'], yes}]), {ok,yes} = report_type([a,b,c], [], [{[a,'_',c], yes}]), {ok,yes} = report_type([a,b,c], [], [{[a,b|'_'], yes}]), - {ok,yes} = report_type([a,b,c], [{type,yes}], [{[a,b,c], no}]), + {ok,yes} = report_type([a,b,c], [{report_type,yes}], [{[a,b,c], no}]), ok. -endif. diff --git a/test/ct.config b/test/ct.config new file mode 100644 index 0000000..62f5cfa --- /dev/null +++ b/test/ct.config @@ -0,0 +1,8 @@ +%% -*- erlang -*- +{lager, [ + {handlers, [ + {lager_console_backend, debug}, + {lager_file_backend, [{file, "error.log"}, {level, error}]}, + {lager_file_backend, [{file, "console.log"}, {level, debug}]} + ]} +]}. diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index f75011d..d5c924b 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -139,25 +139,30 @@ end_per_testcase(Case, Config) when Case == test_folsom_histogram; Case == test_history1_folsom; Case == test_history4_folsom -> - [application:stop(App) || App <- ?config(started_apps, Config)], + _ = stop_started_apps(Config), folsom:stop(), application:stop(bear), ok; end_per_testcase(Case, Config) when Case == test_ext_predef; Case == test_function_match -> - ok = application:unset_env(common_test, exometer_predefined), - [application:stop(App) || App <- ?config(started_apps, Config)], - ok = application:stop(setup), + ok = application:unset_env(stdlib, exometer_predefined), + _ = stop_started_apps(Config), ok; end_per_testcase(test_app_predef, Config) -> + ok = application:unset_env(app1, exometer_predefined), ok = application:stop(app1), - [application:stop(App) || App <- ?config(started_apps, Config)], + _ = stop_started_apps(Config), ok; end_per_testcase(_Case, Config) -> - [application:stop(App) || App <- ?config(started_apps, Config)], + _ = stop_started_apps(Config), ok. +stop_started_apps(Config) -> + [ok = application:stop(App) || + App <- lists:reverse(?config(started_apps, Config))]. + + %%%=================================================================== %%% Test Cases %%%=================================================================== diff --git a/test/exometer_error_SUITE.erl b/test/exometer_error_SUITE.erl new file mode 100644 index 0000000..13ccb35 --- /dev/null +++ b/test/exometer_error_SUITE.erl @@ -0,0 +1,155 @@ +-module(exometer_error_SUITE). + +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% common_test exports +-export( + [ + all/0, groups/0, suite/0, + init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2 + ]). + +%% test case exports +-export( + [ + test_failing_probe/1, + test_escalation_1/1, + test_escalation_2/1 + ]). + +-include_lib("common_test/include/ct.hrl"). + +%%%=================================================================== +%%% common_test API +%%%=================================================================== + +all() -> + [ + {group, test_probes} + ]. + +groups() -> + [ + {test_probes, [shuffle], + [ + test_failing_probe, + test_escalation_1, + test_escalation_2 + ]} + ]. + +suite() -> + []. + +init_per_suite(Config) -> + _ = application:stop(exometer_core), + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(Case, Config) -> + {ok, Started} = exometer_test_util:ensure_all_started(exometer_core), + ct:log("Started: ~p~n", [[{T, catch ets:tab2list(T)} + || T <- exometer_util:tables()]]), + [{started_apps, Started}|Config]. + +end_per_testcase(_Case, Config) -> + ct:log("end_per_testcase(Config = ~p)~n", [Config]), + stop_started_apps(Config), + ok. + +stop_started_apps(Config) -> + [application:stop(A) || A <- lists:reverse(?config(started_apps, Config))], + ok. + +%%%=================================================================== +%%% Test Cases +%%%=================================================================== +test_failing_probe(_Config) -> + M = [?MODULE, ?LINE], + ok = exometer:new(M, histogram, []), + true = killed_probe_restarts(M), + ok. + +test_escalation_1(_Config) -> + M = [?MODULE, ?LINE], + Levels = [{{3,5000}, restart}, + {'_', disable}], + ok = exometer:new(M, histogram, [{restart, Levels}]), + true = killed_probe_restarts(M), + true = killed_probe_restarts(M), + true = killed_probe_restarts(M), + true = killed_probe_disabled(M), + ok. + +test_escalation_2(_Config) -> + M = [?MODULE, ?LINE], + Levels = [{{3,5000}, restart}, + {'_', delete}], + ok = exometer:new(M, histogram, [{restart, Levels}]), + true = killed_probe_restarts(M), + true = killed_probe_restarts(M), + true = killed_probe_restarts(M), + true = killed_probe_deleted(M), + ok. + +killed_probe_restarts(M) -> + Pid = exometer:info(M, ref), + ct:log("Pid = ~p~n", [Pid]), + exit(Pid, kill), + ok = await_death(Pid), + NewPid = exometer:info(M, ref), + ct:log("NewPid = ~p~n", [NewPid]), + enabled = exometer:info(M, status), + true = Pid =/= NewPid. + +killed_probe_disabled(M) -> + Pid = exometer:info(M, ref), + ct:log("Pid = ~p~n", [Pid]), + exit(Pid, kill), + ok = await_death(Pid), + undefined = exometer:info(M, ref), + ct:log("Ref = undefined~n", []), + disabled = exometer:info(M, status), + true. + +killed_probe_deleted(M) -> + Pid = exometer:info(M, ref), + ct:log("Pid = ~p~n", [Pid]), + exit(Pid, kill), + ok = await_death(Pid), + ct:log("Ets = ~p~n", [[{T,ets:tab2list(T)} || + T <- exometer_util:tables()]]), + {error, not_found} = exometer:get_value(M), + ct:log("~p deleted~n", [M]), + true. + +await_death(Pid) -> + Ref = erlang:send_after(1000, self(), zombie), + await_death(Pid, Ref). + +await_death(Pid, Ref) -> + case erlang:read_timer(Ref) of + false -> + error({process_not_dead, Pid}); + _ -> + case erlang:is_process_alive(Pid) of + true -> + erlang:bump_reductions(500), + await_death(Pid, Ref); + false -> + erlang:cancel_timer(Ref), + _ = sys:get_status(exometer_admin), + _ = sys:get_status(exometer_admin), + ok + end + end. From c6d12d4dbdcfe7afd5951cbdd1bf31ab99fbee7b Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Thu, 9 Apr 2015 22:05:24 +0200 Subject: [PATCH 29/40] address dialyzer complaints --- src/exometer.erl | 2 +- src/exometer_proc.erl | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/exometer.erl b/src/exometer.erl index 32706fe..8682c55 100644 --- a/src/exometer.erl +++ b/src/exometer.erl @@ -568,7 +568,7 @@ module_setopts(#exometer_entry{behaviour = probe, {ok, Ref} = exometer_probe:start_probe(E), update_entry_elems(Name, [{#exometer_entry.ref, Ref}]) end; - false -> + _ -> exometer_probe:setopts(E, Options, NewStatus) end, ok; diff --git a/src/exometer_proc.erl b/src/exometer_proc.erl index a2d14ed..7aa3248 100644 --- a/src/exometer_proc.erl +++ b/src/exometer_proc.erl @@ -38,6 +38,7 @@ -module(exometer_proc). -export([spawn_process/2, + spawn_process/3, cast/2, call/2, process_options/1, @@ -77,6 +78,16 @@ spawn_process(Name, F) when is_function(F,0) -> init(Name, Mod, F, Parent) end). +spawn_process(Name, F, Opts) -> + {_, Mod} = erlang:fun_info(F, module), + Parent = self(), + SpawnOpts = proplists:get_value(spawn_opts, Opts, []), + OnError = proplists:get_value(on_error, Opts, delete), + proc_lib:spawn_opt(fun() -> + exometer_admin:monitor(Name, self(), OnError), + init(Name, Mod, F, Parent) + end, SpawnOpts). + init(Name, Mod, StartF, ParentPid) -> I = #info{parent = ParentPid}, Sys = I#info.sys, From 7234262d27ef40939718104963311f2994cc9cb6 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Thu, 9 Apr 2015 22:35:06 +0200 Subject: [PATCH 30/40] fixes after testing with R16B01 --- src/exometer_util.erl | 9 +++------ test/exometer_SUITE.erl | 8 +++++++- test/exometer_error_SUITE.erl | 8 +++++++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/exometer_util.erl b/src/exometer_util.erl index 37269f9..a4e8cba 100644 --- a/src/exometer_util.erl +++ b/src/exometer_util.erl @@ -369,12 +369,9 @@ test_event_flag(update, _) -> false. %% on what's available. -spec ensure_all_started(atom()) -> {ok, [atom()]} | {error, term()}. ensure_all_started(App) -> - case erlang:function_exported(application, ensure_all_started, 1) of - true -> - application:ensure_all_started(App); - false -> - ensure_all_started(App, []) - end. + %% Referencing application:ensure_all_started/1 will anger Xref + %% in earlier R16B versions of OTP + ensure_all_started(App, []). %% This implementation is originally from Basho's %% Webmachine. Reimplementation of ensure_all_started. NOTE this does diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index d5c924b..bf740e5 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -159,9 +159,15 @@ end_per_testcase(_Case, Config) -> ok. stop_started_apps(Config) -> - [ok = application:stop(App) || + [stop_app(App) || App <- lists:reverse(?config(started_apps, Config))]. +stop_app(App) -> + case application:stop(App) of + ok -> ok; + {error, {not_started, _}} -> + ok + end. %%%=================================================================== %%% Test Cases diff --git a/test/exometer_error_SUITE.erl b/test/exometer_error_SUITE.erl index 13ccb35..cb3f6c5 100644 --- a/test/exometer_error_SUITE.erl +++ b/test/exometer_error_SUITE.erl @@ -68,9 +68,15 @@ end_per_testcase(_Case, Config) -> ok. stop_started_apps(Config) -> - [application:stop(A) || A <- lists:reverse(?config(started_apps, Config))], + [stop_app(A) || A <- lists:reverse(?config(started_apps, Config))], ok. +stop_app(App) -> + case application:stop(App) of + ok -> ok; + {error, {not_started,_}} -> ok + end. + %%%=================================================================== %%% Test Cases %%%=================================================================== From b985a4a4654dd93e766a6a6fd97c879ebc5f1e64 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Tue, 14 Apr 2015 20:11:23 +0200 Subject: [PATCH 31/40] cancel timer for triggered intervals --- src/exometer_report.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/exometer_report.erl b/src/exometer_report.erl index d709dc1..9e5dca3 100644 --- a/src/exometer_report.erl +++ b/src/exometer_report.erl @@ -1214,7 +1214,8 @@ restart_subscr_timer(_, _, _) -> restart_batch_timer(Name, #reporter{name = Reporter, intervals = Ints}, T0) when is_list(Ints) -> case lists:keyfind(Name, #interval.name, Ints) of - #interval{time = Time} = I when is_integer(Time) -> + #interval{time = Time, t_ref = OldTRef} = I when is_integer(Time) -> + cancel_timer(OldTRef), TRef = erlang:send_after( adjust_interval(Time, T0), self(), batch_timer_msg(Reporter, Name, Time, T0)), From b327fe1588b1cb04c5fc8d96fcf6a1e955ac6474 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Tue, 14 Apr 2015 20:54:51 +0200 Subject: [PATCH 32/40] fix bugs in logger flow control --- src/exometer_report_logger.erl | 4 +-- test/exometer_report_SUITE.erl | 32 +++++++++++++++++----- test/exometer_test_udp_reporter.erl | 42 ++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/exometer_report_logger.erl b/src/exometer_report_logger.erl index 279f0ce..5271aea 100644 --- a/src/exometer_report_logger.erl +++ b/src/exometer_report_logger.erl @@ -269,8 +269,8 @@ handle_cast({socket, Socket}, #st{input = L} = S) -> case L of #tcp{active = N} = T -> case inet:getopts(Socket, [active]) of - [false] -> - inet:setopts(Socket, {active, N}); + {ok, [{active, false}]} -> + inet:setopts(Socket, [{active, N}]); _ -> ok end, diff --git a/test/exometer_report_SUITE.erl b/test/exometer_report_SUITE.erl index 17131a6..a069103 100644 --- a/test/exometer_report_SUITE.erl +++ b/test/exometer_report_SUITE.erl @@ -23,7 +23,9 @@ test_newentry/1, test_subscribe/1, test_subscribe_find/1, - test_subscribe_select/1 + test_subscribe_select/1, + test_logger_flow_control/1, + test_logger_flow_control_2/1 ]). -behaviour(exometer_report_logger). @@ -37,7 +39,8 @@ all() -> [ - {group, test_reporter} + {group, test_reporter}, + {group, test_logger} ]. groups() -> @@ -48,6 +51,11 @@ groups() -> test_subscribe, test_subscribe_find, test_subscribe_select + ]}, + {test_logger, [], + [ + test_logger_flow_control, + test_logger_flow_control_2 ]} ]. @@ -123,14 +131,21 @@ test_subscribe_select(Config) -> [{_,R1},{_,R2},{_,R3}] = ets:tab2list(Tab), ok. +test_logger_flow_control(Config) -> + ok = test_subscribe_find([{input_port_options, [{active, false}]}|Config]). + +test_logger_flow_control_2(Config) -> + ok = test_subscribe_find([{input_port_options, [{active, once}]}|Config]). start_logger_and_reporter(Reporter, Config) -> Port = get_port(Config), + IPO = config(input_port_options, Config, []), + ct:log("IPO = ~p~n", [IPO]), Res = exometer_report_logger:new( [{id, ?MODULE}, {input, [{mode, plugin}, {module, exometer_test_udp_reporter}, - {state, {Port, []}}]}, + {state, {Port, IPO}}]}, {output, [{mode, plugin}, {module, exometer_test_udp_reporter}]}, {output, [{mode, ets}]}, @@ -162,11 +177,14 @@ ets_tab(Info) -> T. get_port(Config) -> - case ?config(port, Config) of + config(port, Config, ?DEFAULT_PORT). + +config(Key, Config, Default) -> + case ?config(Key, Config) of undefined -> - ?DEFAULT_PORT; - Port -> - Port + Default; + Value -> + Value end. tree_opt([H|T], L) when is_list(L) -> diff --git a/test/exometer_test_udp_reporter.erl b/test/exometer_test_udp_reporter.erl index 3cd6504..d0e673f 100644 --- a/test/exometer_test_udp_reporter.erl +++ b/test/exometer_test_udp_reporter.erl @@ -108,10 +108,13 @@ send(Term, #st{socket = Socket, address = Address, port = Port}) -> logger_init_input({Port, Opts}) -> Parent = self(), - {ok, spawn_link(fun() -> - {ok, Socket} = gen_udp:open(Port, [binary|Opts]), - input_loop(Socket, Parent) - end)}. + {Pid,_} = spawn_monitor( + fun() -> + {ok, Socket} = gen_udp:open(Port, [binary|Opts]), + check_active(Socket, Parent), + input_loop(Socket, Parent) + end), + {ok, Pid}. logger_init_output(_) -> {ok, []}. @@ -120,8 +123,33 @@ logger_handle_data(Data, St) -> {binary_to_term(iolist_to_binary([Data])), St}. input_loop(Socket, Parent) -> - receive - {udp, Socket, _Host, _Port, Data} -> - Parent ! {plugin, self(), Data} + case receive + {udp, Socket, _Host, _Port, Data} -> + Parent ! {plugin, self(), Data}; + {udp_passive, Socket} -> + Parent ! {plugin_passive, self()}, check; + {plugin_active, Active} -> + ct:log("{plugin_active, ~p}", [Active]), + inet:setopts(Socket, [{active, Active}]) + after 1000 -> + io:fwrite(user, "input_loop timeout~n", []) + end of + check -> + check_active(Socket, Parent); + _ -> + ok end, input_loop(Socket, Parent). + +check_active(Socket, Parent) -> + case inet:getopts(Socket, [active]) of + {ok, [{active, A}]} -> + case A of + _ when A==0; A==false; A==once -> + Parent ! {plugin_passive, self()}; + _ -> + ok + end; + _ -> + ok + end. From 24181bdceea7d9320743680482dea080ccb5b9b2 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Wed, 15 Apr 2015 13:24:29 +0200 Subject: [PATCH 33/40] Make slot_histogram test more robust The slot_histogram test tends to fail occasionally, like depending on the fact that the update sequence is slightly timing-sensitive (the slot_slide histograms use 10 ms time slots). This change runs the test several times, and considers it a success if the majority of results match the expectation. --- test/exometer_SUITE.erl | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index 7cdfeff..22ba2e7 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -269,8 +269,15 @@ test_std_histogram(_Config) -> {50,2},{75,3},{90,4},{95,5},{99,8},{999,9}] = scale_mean(DPs), ok. -test_slot_histogram(_Config) -> +test_slot_histogram(Config) -> C = [?MODULE, hist, ?LINE], + ok = majority(5, fun test_slot_histogram_/1, [{metric_name, C}|Config]). + +test_slot_histogram_({cleanup, Config}) -> + C = ?config(metric_name, Config), + exometer:delete(C); +test_slot_histogram_(Config) -> + C = ?config(metric_name, Config), ok = exometer:new(C, histogram, [{histogram_module, exometer_slot_slide}, {keep_high, 100}, {truncate, false}]), @@ -538,3 +545,26 @@ compile_app1(Config) -> Path = filename:join(Dir, "ebin"), PRes = code:add_pathz(Path), ct:log("add_pathz(~p) -> ~p~n", [Path, PRes]). + +majority(N, F, Cfg) -> + majority(N, F, Cfg, []). + +majority(0, _, _, Hist) -> + Failed = [1 || {caught,_,_} <- Hist], + ct:log("majority(): Failed = ~p, Hist=~p~n", [Failed, Hist]), + case {length(Failed), length(Hist)} of + {Lf, L} when Lf >= L div 2 -> + {error, {too_many_failures, Hist}}; + _ -> + ok + end; +majority(N, F, Cfg, Hist) when N > 0 -> + Res = try F(Cfg) + catch + C:R -> + {caught, C, R} + after + F({cleanup, Cfg}) + end, + majority(N-1, F, Cfg, [Res|Hist]). + From ed216ddb165e2fbbacb973f38f7396cde05241bc Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Wed, 15 Apr 2015 18:08:49 +0200 Subject: [PATCH 34/40] move majority fn to test_util --- test/exometer_SUITE.erl | 25 ++----------------------- test/exometer_test_util.erl | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index 22ba2e7..c1893c1 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -49,6 +49,8 @@ vals/0 ]). +-import(exometer_test_util, [majority/3]). + -include_lib("common_test/include/ct.hrl"). %%%=================================================================== @@ -545,26 +547,3 @@ compile_app1(Config) -> Path = filename:join(Dir, "ebin"), PRes = code:add_pathz(Path), ct:log("add_pathz(~p) -> ~p~n", [Path, PRes]). - -majority(N, F, Cfg) -> - majority(N, F, Cfg, []). - -majority(0, _, _, Hist) -> - Failed = [1 || {caught,_,_} <- Hist], - ct:log("majority(): Failed = ~p, Hist=~p~n", [Failed, Hist]), - case {length(Failed), length(Hist)} of - {Lf, L} when Lf >= L div 2 -> - {error, {too_many_failures, Hist}}; - _ -> - ok - end; -majority(N, F, Cfg, Hist) when N > 0 -> - Res = try F(Cfg) - catch - C:R -> - {caught, C, R} - after - F({cleanup, Cfg}) - end, - majority(N-1, F, Cfg, [Res|Hist]). - diff --git a/test/exometer_test_util.erl b/test/exometer_test_util.erl index c6ada43..7c8baa4 100644 --- a/test/exometer_test_util.erl +++ b/test/exometer_test_util.erl @@ -1,6 +1,7 @@ -module(exometer_test_util). --export([ensure_all_started/1]). +-export([ensure_all_started/1, + majority/3]). %% This implementation is originally from Basho's Webmachine. On %% older versions of Erlang, we don't have @@ -32,3 +33,29 @@ ensure_all_started(App, Apps0) -> {ok, Apps} = ensure_all_started(BaseApp, Apps0), ensure_all_started(App, [BaseApp|Apps]) end. + +%% Run test N times. Success if a majority of the tests succeed. +%% Cleanup between runs done by calling F({cleanup, Config}) +%% Returns 'ok' or {error, Info}. +%% +majority(N, F, Cfg) -> + majority(N, F, Cfg, []). + +majority(0, _, _, Hist) -> + Failed = [1 || {caught,_,_} <- Hist], + ct:log("majority(): Failed = ~p, Hist=~p~n", [Failed, Hist]), + case {length(Failed), length(Hist)} of + {Lf, L} when Lf >= L div 2 -> + {error, {too_many_failures, Hist}}; + _ -> + ok + end; +majority(N, F, Cfg, Hist) when N > 0 -> + Res = try F(Cfg) + catch + C:R -> + {caught, C, R} + after + F({cleanup, Cfg}) + end, + majority(N-1, F, Cfg, [Res|Hist]). From 22aa37e49fca8df49173ba7a0886b192615d851f Mon Sep 17 00:00:00 2001 From: Tino Breddin Date: Thu, 16 Apr 2015 16:11:28 +0200 Subject: [PATCH 35/40] add OTP 17.4 to CI queue --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2e9a497..114b3ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false language: erlang script: "make ci" otp_release: + - 17.4 - 17.3 - 17.1 - 17.0 From 54f15cd7d4403d4a530035bfb3dcedf899b5b4a6 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 17 Apr 2015 16:24:26 +0200 Subject: [PATCH 36/40] Re-generated docs --- README.md | 10 +- doc/README.md | 10 +- doc/edoc-info | 6 +- doc/exometer.md | 9 +- doc/exometer_admin.md | 11 +- doc/exometer_alias.md | 9 +- doc/exometer_cache.md | 9 +- doc/exometer_probe.md | 47 ++++- doc/exometer_proc.md | 9 +- doc/exometer_report.md | 43 ++++- doc/exometer_report_logger.md | 271 +++++++++++++++++++++++++++++ doc/exometer_util.md | 27 ++- src/exometer_global.erl | 10 ++ src/exometer_report_logger_sup.erl | 1 + 14 files changed, 455 insertions(+), 17 deletions(-) create mode 100644 doc/exometer_report_logger.md diff --git a/README.md b/README.md index 5ac2b48..37566a3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. -__Version:__ Dec 3 2014 19:51:33 +__Version:__ Apr 17 2015 14:08:02 __Authors:__ Ulf Wiger ([`ulf.wiger@feuerlabs.com`](mailto:ulf.wiger@feuerlabs.com)), Magnus Feuer ([`magnus.feuer@feuerlabs.com`](mailto:magnus.feuer@feuerlabs.com)). @@ -474,6 +474,13 @@ Exometer Core defaults can be changed either through OTP application environment variables or through the use of Basho's `cuttlefish` ([`https://github.com/basho/cuttlefish`](https://github.com/basho/cuttlefish)). +__Note:__ Exometer Core will check both the `exometer` and the `exometer_core` +application environments. The `exometer` environment overrides the +`exometer_core` environment. However, if only `exometer_core` is used, any +`exometer` environment will simply be ignored. This is because of the +application controller: environment data is not loaded until the application +in question is loaded. + ####
Configuring type - entry maps #### @@ -734,6 +741,7 @@ processing is complete. exometer_proc exometer_report exometer_report_lager +exometer_report_logger exometer_report_tty exometer_shallowtree exometer_slide diff --git a/doc/README.md b/doc/README.md index 9c1c4e4..e212344 100644 --- a/doc/README.md +++ b/doc/README.md @@ -4,7 +4,7 @@ Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. -__Version:__ Dec 3 2014 19:51:33 +__Version:__ Apr 17 2015 14:08:02 __Authors:__ Ulf Wiger ([`ulf.wiger@feuerlabs.com`](mailto:ulf.wiger@feuerlabs.com)), Magnus Feuer ([`magnus.feuer@feuerlabs.com`](mailto:magnus.feuer@feuerlabs.com)). @@ -474,6 +474,13 @@ Exometer Core defaults can be changed either through OTP application environment variables or through the use of Basho's `cuttlefish` ([`https://github.com/basho/cuttlefish`](https://github.com/basho/cuttlefish)). +__Note:__ Exometer Core will check both the `exometer` and the `exometer_core` +application environments. The `exometer` environment overrides the +`exometer_core` environment. However, if only `exometer_core` is used, any +`exometer` environment will simply be ignored. This is because of the +application controller: environment data is not loaded until the application +in question is loaded. + #### Configuring type - entry maps #### @@ -734,6 +741,7 @@ processing is complete. exometer_proc exometer_report exometer_report_lager +exometer_report_logger exometer_report_tty exometer_shallowtree exometer_slide diff --git a/doc/edoc-info b/doc/edoc-info index e671710..a78b3ee 100644 --- a/doc/edoc-info +++ b/doc/edoc-info @@ -5,6 +5,6 @@ exometer_cpu,exometer_duration,exometer_entry,exometer_folsom, exometer_folsom_monitor,exometer_function,exometer_histogram, exometer_igor,exometer_info,exometer_probe,exometer_proc, - exometer_report,exometer_report_lager,exometer_report_tty, - exometer_shallowtree,exometer_slide,exometer_slot_slide, - exometer_spiral,exometer_uniform,exometer_util]}. + exometer_report,exometer_report_lager,exometer_report_logger, + exometer_report_tty,exometer_shallowtree,exometer_slide, + exometer_slot_slide,exometer_spiral,exometer_uniform,exometer_util]}. diff --git a/doc/exometer.md b/doc/exometer.md index 2b3765b..04f9806 100644 --- a/doc/exometer.md +++ b/doc/exometer.md @@ -181,7 +181,7 @@ value() = any() ## Function Index ## -
aggregate/2Aggregate datapoints of matching entries.
create_entry/1
delete/1Delete the metric.
ensure/3Ensure that metric exists and is of given type.
find_entries/1Find metrics based on a name prefix pattern.
get_value/1Fetch the current value of the metric.
get_value/2
get_values/1
info/1Returns a list of info items for Metric, see info/2.
info/2Retrieves information about a metric.
new/2Equivalent to new(Name, Type, []).
new/3Create a new metrics entry.
propose/3Propose a new exometer entry (no entry actually created).
re_register/3Create a new metrics entry, overwrite any old entry.
register_application/0Equivalent to register_application(current_application()).
register_application/1Registers statically defined entries with exometer.
repair/1Delete and re-create an entry.
reset/1Reset the metric.
sample/1Tells the metric (mainly probes) to take a sample.
select/1Perform an ets:select() on the set of metrics.
select/2Perform an ets:select() with a Limit on the set of metrics.
select_cont/1Equivalent to ets:select(Cont).
select_count/1Corresponds to ets:select_count/1.
setopts/2Change options for the metric.
start/0Start exometer and dependent apps (for testing).
stop/0Stop exometer and dependent apps (for testing).
update/2Update the given metric with Value.
update_or_create/2Update existing metric, or create+update according to template.
update_or_create/4
+
aggregate/2Aggregate datapoints of matching entries.
create_entry/1
delete/1Delete the metric.
ensure/3Ensure that metric exists and is of given type.
find_entries/1Find metrics based on a name prefix pattern.
get_value/1Fetch the current value of the metric.
get_value/2
get_values/1
global_status/1
info/1Returns a list of info items for Metric, see info/2.
info/2Retrieves information about a metric.
new/2Equivalent to new(Name, Type, []).
new/3Create a new metrics entry.
propose/3Propose a new exometer entry (no entry actually created).
re_register/3Create a new metrics entry, overwrite any old entry.
register_application/0Equivalent to register_application(current_application()).
register_application/1Registers statically defined entries with exometer.
repair/1Delete and re-create an entry.
reset/1Reset the metric.
sample/1Tells the metric (mainly probes) to take a sample.
select/1Perform an ets:select() on the set of metrics.
select/2Perform an ets:select() with a Limit on the set of metrics.
select_cont/1Equivalent to ets:select(Cont).
select_count/1Corresponds to ets:select_count/1.
setopts/2Change options for the metric.
start/0Start exometer and dependent apps (for testing).
stop/0Stop exometer and dependent apps (for testing).
update/2Update the given metric with Value.
update_or_create/2Update existing metric, or create+update according to template.
update_or_create/4
@@ -337,6 +337,13 @@ get_value(Name::name(), DataPoint:: + +### global_status/1 ### + +`global_status(St) -> any()` + + ### info/1 ### diff --git a/doc/exometer_admin.md b/doc/exometer_admin.md index 87a82a7..6b1f50d 100644 --- a/doc/exometer_admin.md +++ b/doc/exometer_admin.md @@ -11,7 +11,7 @@ __Behaviours:__ [`gen_server`](gen_server.md).
auto_create_entry/1
code_change/3
delete_entry/1
demonitor/1
ensure/3
find_auto_template/1Convenience function for testing which template will apply to -Name.
handle_call/3
handle_cast/2
handle_info/2
init/1
load_defaults/0
load_predefined/0
make_patterns/2
monitor/2
new_entry/3
normalize_name/1
prefixes/1
preset_defaults/0
propose/3
re_register_entry/3
register_application/1
repair_entry/1
set_default/3Sets a default definition for a metric type, possibly using wildcards.
start_link/0
terminate/2
+Name.handle_call/3handle_cast/2handle_info/2init/1load_defaults/0load_predefined/0make_patterns/2monitor/2monitor/3new_entry/3normalize_name/1prefixes/1preset_defaults/0propose/3re_register_entry/3register_application/1repair_entry/1set_default/3Sets a default definition for a metric type, possibly using wildcards.start_link/0terminate/2 @@ -69,7 +69,7 @@ Convenience function for testing which template will apply to ### handle_call/3 ### -`handle_call(X1, From, S) -> any()` +`handle_call(Req, From, S) -> any()` @@ -121,6 +121,13 @@ Convenience function for testing which template will apply to `monitor(Name, Pid) -> any()` + + +### monitor/3 ### + +`monitor(Name, Pid, OnError) -> any()` + + ### new_entry/3 ### diff --git a/doc/exometer_alias.md b/doc/exometer_alias.md index ddba74e..1246da5 100644 --- a/doc/exometer_alias.md +++ b/doc/exometer_alias.md @@ -128,13 +128,20 @@ stat_map() = [{name(), [{dp(), < ## Function Index ## -
delete/1Delete an alias, if it exists in the registry.
get_value/1Resolve the given alias and return corresponding metric and value.
load/1Load a list of mappings between entry+datapoint pairs and aliases.
new/3Create a new alias.
prefix_foldl/3Fold (ascending order) over the aliases matching Prefix.
prefix_foldr/3Fold (descending order) over the aliases matching Prefix.
prefix_match/1List all aliases matching the given prefix.
regexp_foldl/3Fold (ascending order) over the aliases matching Regexp.
regexp_foldr/3Fold (descending order) over the aliases matching Regexp.
resolve/1Look up an alias in the registry and return corresponding mapping.
reverse_map/2List all aliases mapped to the given entry+datapoint pair(s).
start_link/0
unload/1Unload a list of mappings.
update/2Resolves the given alias and updates the corresponding entry (if any).
+
check_map/1
delete/1Delete an alias, if it exists in the registry.
get_value/1Resolve the given alias and return corresponding metric and value.
load/1Load a list of mappings between entry+datapoint pairs and aliases.
new/3Create a new alias.
prefix_foldl/3Fold (ascending order) over the aliases matching Prefix.
prefix_foldr/3Fold (descending order) over the aliases matching Prefix.
prefix_match/1List all aliases matching the given prefix.
regexp_foldl/3Fold (ascending order) over the aliases matching Regexp.
regexp_foldr/3Fold (descending order) over the aliases matching Regexp.
resolve/1Look up an alias in the registry and return corresponding mapping.
reverse_map/2List all aliases mapped to the given entry+datapoint pair(s).
start_link/0
unload/1Unload a list of mappings.
update/2Resolves the given alias and updates the corresponding entry (if any).
## Function Details ## + + +### check_map/1 ### + +`check_map(Map) -> any()` + + ### delete/1 ### diff --git a/doc/exometer_cache.md b/doc/exometer_cache.md index f88e802..b679075 100644 --- a/doc/exometer_cache.md +++ b/doc/exometer_cache.md @@ -10,7 +10,7 @@ __Behaviours:__ [`gen_server`](gen_server.md). ## Function Index ## -
code_change/3
delete/2
handle_call/3
handle_cast/2
handle_info/2
init/1
read/2
start_link/0
terminate/2
write/3
write/4
+
code_change/3
delete/2
delete_name/1
handle_call/3
handle_cast/2
handle_info/2
init/1
read/2
start_link/0
terminate/2
write/3
write/4
@@ -31,6 +31,13 @@ __Behaviours:__ [`gen_server`](gen_server.md). `delete(Name, DataPoint) -> any()` + + +### delete_name/1 ### + +`delete_name(Name) -> any()` + + ### handle_call/3 ### diff --git a/doc/exometer_probe.md b/doc/exometer_probe.md index cb0a36f..8f3582b 100644 --- a/doc/exometer_probe.md +++ b/doc/exometer_probe.md @@ -603,15 +603,53 @@ process when it receives a message that is not recognized by the internal receive loop. + The implementation of `probe_handle_msg/2` shall return `{ok, NewState}`, where `NewState` contains the new state of the probe that reflects the processed message. + + + + +### Fault tolerance ### + + +Probes are supervised by the `exometer_admin` process, and can be restarted +after a crash. Restart parameters are provided via the option +`{restart, Params}`, where `Params` is a list of `{Frequency, Action}` +tuples. `Frequency` is either `{Count, MilliSecs}` or `'_`', and +the corresponding `Action :: restart | disable | delete` will be performed +if the frequency of restarts falls within the given limit. + + + +For example, `[{{3, 1000}, restart}]` will allow 3 restarts within a 1-second +window. The matching is performed from top to bottom, and the first matching +pattern is acted upon. If no matching pattern is found, the default action +is `delete`. A pattern `{'_', Action}` acts as a catch-all, and should +be put last in the list. + + + +It is also possible to specify `{{Count, '_'}, Action}`, which means +that a total of `Count` restarts is permitted, regardless of how far apart +they are. The count is reset after each restart. + + +Example: + +```erlang + + {restart, [{{3,1000}, restart}, % up to 3 restarts within 1 sec + {{4,'_'} , disable}, % a total of up to 4 restarts + {'_' , delete}]} % anything else +``` ## Function Index ## -
behaviour/0
delete/3
get_datapoints/3
get_value/3
get_value/4
new/3
reset/3
sample/3
setopts/3
update/4
+
behaviour/0
delete/3
get_datapoints/3
get_value/3
get_value/4
new/3
reset/3
restart/4
sample/3
setopts/3
update/4
@@ -667,6 +705,13 @@ that reflects the processed message. `reset(Name, Type, Pid) -> any()` + + +### restart/4 ### + +`restart(Name, Module, Error, SpawnOpts) -> any()` + + ### sample/3 ### diff --git a/doc/exometer_proc.md b/doc/exometer_proc.md index 770cd09..a4477ce 100644 --- a/doc/exometer_proc.md +++ b/doc/exometer_proc.md @@ -37,7 +37,7 @@ following messages: ## Function Index ## -
call/2Make a synchronous call to an exometer_proc process.
cast/2Send an asynchronous message to an exometer_proc process.
format_status/2
handle_system_msg/4
process_options/1Apply process_flag-specific options.
spawn_process/2Spawn an exometer_proc process.
stop/0Terminate probe process in an orderly way.
system_code_change/4
system_continue/3
system_terminate/4
+
call/2Make a synchronous call to an exometer_proc process.
cast/2Send an asynchronous message to an exometer_proc process.
format_status/2
handle_system_msg/4
process_options/1Apply process_flag-specific options.
spawn_process/2Spawn an exometer_proc process.
spawn_process/3
stop/0Terminate probe process in an orderly way.
system_code_change/4
system_continue/3
system_terminate/4
@@ -130,6 +130,13 @@ Spawn an `exometer_proc` process. This function sets up appropriate monitoring, and calls the function `F` which needs to initialize the probe and enter an event loop. (Note: `exometer_proc` processes are responsible for their own event loop). + + +### spawn_process/3 ### + +`spawn_process(Name, F, Opts) -> any()` + + ### stop/0 ### diff --git a/doc/exometer_report.md b/doc/exometer_report.md index 7a4cf40..2d08295 100644 --- a/doc/exometer_report.md +++ b/doc/exometer_report.md @@ -369,7 +369,7 @@ time_ms() = pos_integer()
add_reporter/2Add a reporter.
call_reporter/2Send a custom (synchronous) call to Reporter.
cast_reporter/2Send a custom (asynchronous) cast to Reporter.
delete_interval/2Delete a named interval.
disable_me/2Used by a reporter to disable itself.
disable_reporter/1Disable Reporter.
enable_reporter/1Enable Reporter.
get_intervals/1List the named intervals for Reporter.
list_metrics/0Equivalent to list_metrics([]).
list_metrics/1List all metrics matching Path, together with subscription status.
list_reporters/0List the name and pid of each known reporter.
list_subscriptions/1List all subscriptions for Reporter.
new_entry/1Called by exometer whenever a new entry is created.
remove_reporter/1Remove reporter and all its subscriptions.
remove_reporter/2Remove Reporter (non-blocking call).
restart_intervals/1Restart all named intervals, respecting specified delays.
set_interval/3Specify a named interval.
setopts/3Called by exometer when options of a metric entry are changed.
start_link/0Starts the server --------------------------------------------------------------------.
start_reporters/0
subscribe/4Equivalent to subscribe(Reporter, Metric, DataPoint, Interval, [], false).
subscribe/5Equivalent to subscribe(Reporter, Metric, DataPoint, Interval, Extra, -false).
subscribe/6Add a subscription to an existing reporter.
terminate_reporter/1
unsubscribe/3Equivalent to unsubscribe(Reporter, Metric, DataPoint, []).
unsubscribe/4Removes a subscription.
unsubscribe_all/2Removes all subscriptions related to Metric in Reporter.
+false).subscribe/6Add a subscription to an existing reporter.terminate_reporter/1trigger_interval/2Trigger a named interval.unsubscribe/3Equivalent to unsubscribe(Reporter, Metric, DataPoint, []).unsubscribe/4Removes a subscription.unsubscribe_all/2Removes all subscriptions related to Metric in Reporter. @@ -412,12 +412,19 @@ subscriptions to the reporter. `{intervals, [named_interval()]}` named_interval() :: {Name::atom(), Interval::pos_integer()} | {Name::atom(), Interval::time_ms(), delay()::time_ms()} +| {Name::atom(), 'manual'} Define named intervals. The name can be used by subscribers, so that all subsriptions for a given named interval will be reported when the interval triggers. An optional delay (in ms) can be given: this will cause the first interval to start in `Delay` milliseconds. When all intervals are named at the same time, the delay parameter can be used to achieve staggered -reporting. +reporting. If the interval is specified as +``` + 'manual +``` +', it will have +to be triggered manually using [`trigger_interval/2`](#trigger_interval-2). + ### call_reporter/2 ### @@ -656,10 +663,16 @@ set_interval(Reporter::reporter_name(), Name:: Specify a named interval. - See [`add_reporter/2`](#add_reporter-2) for a description of named intervals. The named interval is here specified as either `Time` (milliseconds) or -`{Time, Delay}`, where a delay in milliseconds is provided. +`{Time, Delay}`, where a delay in milliseconds is provided. It is also +specify an interval as +``` + 'manual +``` + +', indicating that the interval can +only be triggered manually via [`trigger_interval/2`](#trigger_interval-2). If the named interval exists, it will be replaced with the new definition. @@ -765,6 +778,28 @@ even if the metric cannot be read. `terminate_reporter(Reporter) -> any()` + + +### trigger_interval/2 ### + + +

+trigger_interval(Reporter::reporter_name(), Name::atom()) -> ok
+
+
+ + +Trigger a named interval. + + +This function is mainly used to trigger intervals defined as +``` + 'manual +``` +', +but can be used to trigger any named interval. If a named interval with +a specified time in milliseconds is triggered this way, it will effectively +be restarted, and will repeat as usual from that point on. ### unsubscribe/3 ### diff --git a/doc/exometer_report_logger.md b/doc/exometer_report_logger.md new file mode 100644 index 0000000..696742e --- /dev/null +++ b/doc/exometer_report_logger.md @@ -0,0 +1,271 @@ + + +# Module exometer_report_logger # +* [Description](#description) +* [Data Types](#types) +* [Function Index](#index) +* [Function Details](#functions) + + +Exometer report collector and logger. +__Behaviours:__ [`gen_server`](gen_server.md). + +__This module defines the `exometer_report_logger` behaviour.__
Required callback functions: `logger_init_input/1`, `logger_init_output/1`, `logger_handle_data/2`. + + +## Description ## + + + +This module implements a behavior for collecting reporting data and +handling it (logging to disk or ets, printing to tty, etc.) + + + +The logger has built-in support for receiving input via UDP, TCP or +internal Erlang messaging, as well as a plugin API for custom input +handling. Correspondingly, it has support for output to TTY or ets, as +well as a plugin API for custom output. + + + +An example of how the logger can be used can be found in +`test/exometer_test_udp_reporter.erl`, which implements a UDP-based +reporter as well as an input plugin and an output plugin. This reporter +is used by `test/exometer_report_SUITE.erl`. + + + +Loggers can be combined, e.g. by creating one logger that receives Erlang +messages, and other loggers that receive from different sources, prefix +their reports and pass them on to the first logger. + + + + +## Input plugins ## + + + +An input plugin is initiated by `Module:logger_init_input(State)`, where +`State` is whatever was given as a `state` option (default: `undefined`). +The function must create a process and return `{ok, Pid}`. `Pid` is +responsible for setting up whatever input channel is desired, and passes +on incoming data to the logger via Erlang messages `{plugin, Pid, Data}`. + + + + +## Output Chaining ## + + + +Each incoming data item is passed through the list of output operators. +Each output operator is able to modify the data (the `tty` and `ets` +operators leave the data unchanged). Output plugins receive the data +in `Module:logger_handle_data(Data, State)`, which must return +`{NewData, NewState}`. The state is private to the plugin, while `NewData` +will be passed along to the next output operator. + + + + +## Flow control ## + + + +The logger will handle flow control automatically for `udp` and `tcp` +inputs. If `{active,once}` or `{active, false}`, the logger will trigger +`{active, once}` each time it has handled an incoming message. +If `{active, N}`, it will "refill" the port each time it receives an +indication that it has become passive. + + +Input plugins create a process in `Module:logger_init_input/1`. This process +can mimick the behavior of Erlang ports by sending a `{plugin_passive, Pid}` +message to the logger. The logger will reply with a message, +`{plugin_active, N}`, where `N` is the value given by the `active` option. + + +## Data Types ## + + + + +### logger_info() ### + + + +

+logger_info() = {id, any()} | {input, proplist()} | {output, proplist()}
+
+ + + + + +### proplist() ### + + + +

+proplist() = [{atom(), any()}]
+
+ + + + +## Function Index ## + + +
info/0List active logger instances.
info/1Lists the settings of a given logger instance.
new/1Create a new logger instance.
start_link/1Start function for logger instance.
+ + + + +## Function Details ## + + + +### info/0 ### + + +

+info() -> [{pid(), [logger_info()]}]
+
+
+ + +List active logger instances. + + +This function lists the instances started via [`new/1`](#new-1), along with their +respective settings as nested property lists. + + +### info/1 ### + + +

+info(P::pid()) -> [logger_info()]
+
+
+ +Lists the settings of a given logger instance. + + +### new/1 ### + + +

+new(Options::[{id, any()} | {input, list()} | {output, list()}]) -> {ok, pid()}
+
+
+ + +Create a new logger instance. + + + +This function creates a logger process with the given input and output +parameters. + + + +* `{id, ID}` is mainly for documentation and simplifying identification +of instances returned by [`info/0`](#info-0). +* `{input, PropList}` specifies what the logger listens to. Only the first +`input` entry is regarded, but the option is mandatory. +* `{output, PropList}` specifies what the logger should to with received +data. Multiple `output` entries are allowed, and they will be processed +in the order given. + + + +Valid input options: + + + +* `{mode, udp | tcp | internal | plugin}` defines the protocol +* `{active, false | true | once | N}` provides flow control. Default: `true`. +* (mode-specific options) + + + +Valid output options: + + + +* `{mode, tty | ets | plugin | internal}` defines output types +* (output-specific options) + + + +Mode-specific options, `udp`: + + + +* `{port, integer()}` - UDP port number +* `{options, list()}` - Options to pass to [`gen_udp:open/2`](gen_udp.md#open-2) + + + +Mode-specific options, `tcp`: + + + +* `{port, integer()}` - TCP port number +* `{options, list()}` - Options to pass to [`gen_tcp:listen/2`](gen_tcp.md#listen-2) + + + +Mode-specific options, `tty`: + + + +* `{prefix, iolist()}` - Prefix string inserted before the data, which is +printed as-is (note that any delimiter would need to be part of the prefix) + + + +Mode-specific options, `ets`: +* `{table, ets:table()}` - Ets table identifier. If not specified, an +ordered-set table will be created by the logger process. The incoming +data will be inserted as `{erlang:now(), Data}`. + + + +Mode-specific options, `internal`: +* `{process, PidOrRegname}` specifies another logger instance, which is to +receive data from this logger (if used in output), or which is allowed +to send to this logger (if used in input). If no process is given for +input, any process can send data (on the form +`{exometer_report_logger, Pid, Data}`) to this logger. + + + +Mode-specific options, `plugin`: + + +* `{module, Module}` - name of callback module +(behaviour: `exometer_report_logger`) +* `{state, State}` - Passed as initial argument to +`Module:logger_init_input/1` or `Module:logger_init_output/1`, depending +on whether the plugin is specified as input or output. + + +### start_link/1 ### + + +

+start_link(Options::proplist()) -> {ok, pid()}
+
+
+ + +Start function for logger instance. + + +This function is the start function eventually called as a result from +[`new/1`](#new-1), but whereas `new/1` creates a supervised instance, this +function simply creates the process. It would normally not be used directly. diff --git a/doc/exometer_util.md b/doc/exometer_util.md index 293a5fe..65f3803 100644 --- a/doc/exometer_util.md +++ b/doc/exometer_util.md @@ -32,7 +32,7 @@ timestamp() = non_neg_integer()
clear_event_flag/2
drop_duplicates/1 -drop_duplicates/1 will drop all duplicate elements from a list of tuples identified by their first element.
get_datapoints/1
get_env/2
get_opt/3
get_statistics/3Calculate statistics from a sorted list of values.
get_statistics2/4
get_status/1
histogram/1
histogram/2
pick_items/2Pick values from specified positions in a sorted list of numbers.
report_type/3
set_call_count/2
set_call_count/3
set_event_flag/2
set_status/2
table/0
tables/0
test_event_flag/2
timestamp/0Generate a millisecond-resolution timestamp.
timestamp_to_datetime/1Convert timestamp to a regular datetime.
+drop_duplicates/1 will drop all duplicate elements from a list of tuples identified by their first element.ensure_all_started/1get_datapoints/1get_env/2get_opt/2get_opt/3get_statistics/3Calculate statistics from a sorted list of values.get_statistics2/4get_status/1histogram/1histogram/2perc/2pick_items/2Pick values from specified positions in a sorted list of numbers.report_type/3set_call_count/2set_call_count/3set_event_flag/2set_status/2table/0tables/0test_event_flag/2timestamp/0Generate a millisecond-resolution timestamp.timestamp_to_datetime/1Convert timestamp to a regular datetime. @@ -60,6 +60,17 @@ drop_duplicates(List0::[tuple()]) -> [tuple()] `drop_duplicates/1` will drop all duplicate elements from a list of tuples identified by their first element. Elements which are not tuples will be dropped as well. If called with a non-list argument, the argument is returned as is. + + +### ensure_all_started/1 ### + + +

+ensure_all_started(App::atom()) -> {ok, [atom()]} | {error, term()}
+
+
+ + ### get_datapoints/1 ### @@ -74,6 +85,13 @@ If called with a non-list argument, the argument is returned as is. `get_env(Key, Default) -> any()` + + +### get_opt/2 ### + +`get_opt(K, Opts) -> any()` + + ### get_opt/3 ### @@ -140,6 +158,13 @@ Fulpatchad med min/max av Magnus Feuer. `histogram(Values, DataPoints) -> any()` + + +### perc/2 ### + +`perc(P, Len) -> any()` + + ### pick_items/2 ### diff --git a/src/exometer_global.erl b/src/exometer_global.erl index f35fa0e..e30c1a4 100644 --- a/src/exometer_global.erl +++ b/src/exometer_global.erl @@ -1,3 +1,13 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @private -module(exometer_global). -export([status/0]). diff --git a/src/exometer_report_logger_sup.erl b/src/exometer_report_logger_sup.erl index f86c073..0031ced 100644 --- a/src/exometer_report_logger_sup.erl +++ b/src/exometer_report_logger_sup.erl @@ -7,6 +7,7 @@ %% file, You can obtain one at http://mozilla.org/MPL/2.0/. %% %% ------------------------------------------------------------------- +%% @private -module(exometer_report_logger_sup). -behaviour(supervisor). From a690640b9f3c7d736dd2dce0e82ff731a97ecc30 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 17 Apr 2015 18:30:01 +0200 Subject: [PATCH 37/40] More timing-robust test execution Make more use of the majority() function in the test suites Use 5 attempts as the default when using majority/2 Increase timeout in the reporting test --- test/exometer_SUITE.erl | 4 ++-- test/exometer_error_SUITE.erl | 34 ++++++++++++++++++++++++----- test/exometer_report_SUITE.erl | 28 +++++++++++++++++++++++- test/exometer_test_udp_reporter.erl | 2 +- test/exometer_test_util.erl | 4 ++++ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index dd3dd7a..b32f195 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -49,7 +49,7 @@ vals/0 ]). --import(exometer_test_util, [majority/3]). +-import(exometer_test_util, [majority/2]). -include_lib("common_test/include/ct.hrl"). @@ -280,7 +280,7 @@ test_std_histogram(_Config) -> test_slot_histogram(Config) -> C = [?MODULE, hist, ?LINE], - ok = majority(5, fun test_slot_histogram_/1, [{metric_name, C}|Config]). + ok = majority(fun test_slot_histogram_/1, [{metric_name, C}|Config]). test_slot_histogram_({cleanup, Config}) -> C = ?config(metric_name, Config), diff --git a/test/exometer_error_SUITE.erl b/test/exometer_error_SUITE.erl index cb3f6c5..54d1ea6 100644 --- a/test/exometer_error_SUITE.erl +++ b/test/exometer_error_SUITE.erl @@ -25,6 +25,8 @@ test_escalation_2/1 ]). +-import(exometer_test_util, [majority/2]). + -include_lib("common_test/include/ct.hrl"). %%%=================================================================== @@ -56,7 +58,7 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. -init_per_testcase(Case, Config) -> +init_per_testcase(_Case, Config) -> {ok, Started} = exometer_test_util:ensure_all_started(exometer_core), ct:log("Started: ~p~n", [[{T, catch ets:tab2list(T)} || T <- exometer_util:tables()]]), @@ -80,14 +82,27 @@ stop_app(App) -> %%%=================================================================== %%% Test Cases %%%=================================================================== -test_failing_probe(_Config) -> +test_failing_probe(Config) -> M = [?MODULE, ?LINE], + majority(fun test_failing_probe_/1, [{metric_name, M}|Config]). + +test_failing_probe_({cleanup, Config}) -> + M = ?config(metric_name, Config), + exometer:delete(M); +test_failing_probe_(Config) -> + M = ?config(metric_name, Config), ok = exometer:new(M, histogram, []), true = killed_probe_restarts(M), ok. -test_escalation_1(_Config) -> +test_escalation_1(Config) -> M = [?MODULE, ?LINE], + majority(fun test_escalation_1_/1, [{metric_name, M}|Config]). + +test_escalation_1_({cleanup, Config}) -> + exometer:delete(?config(metric_name, Config)); +test_escalation_1_(Config) -> + M = ?config(metric_name, Config), Levels = [{{3,5000}, restart}, {'_', disable}], ok = exometer:new(M, histogram, [{restart, Levels}]), @@ -97,8 +112,14 @@ test_escalation_1(_Config) -> true = killed_probe_disabled(M), ok. -test_escalation_2(_Config) -> +test_escalation_2(Config) -> M = [?MODULE, ?LINE], + majority(fun test_escalation_2_/1, [{metric_name, M}|Config]). + +test_escalation_2_({cleanup, Config}) -> + exometer:delete(?config(metric_name, Config)); +test_escalation_2_(Config) -> + M = ?config(metric_name, Config), Levels = [{{3,5000}, restart}, {'_', delete}], ok = exometer:new(M, histogram, [{restart, Levels}]), @@ -141,7 +162,10 @@ killed_probe_deleted(M) -> await_death(Pid) -> Ref = erlang:send_after(1000, self(), zombie), - await_death(Pid, Ref). + await_death(Pid, Ref), + %% Now ping exometer_admin twice to give it time to handle the DOWN msg + sys:get_status(exometer_admin), + sys:get_status(exometer_admin). await_death(Pid, Ref) -> case erlang:read_timer(Ref) of diff --git a/test/exometer_report_SUITE.erl b/test/exometer_report_SUITE.erl index a069103..6306ab5 100644 --- a/test/exometer_report_SUITE.erl +++ b/test/exometer_report_SUITE.erl @@ -32,6 +32,8 @@ -export([logger_init_output/1, logger_handle_data/2]). +-import(exometer_test_util, [majority/2]). + -include_lib("common_test/include/ct.hrl"). -include_lib("exometer_core/include/exometer.hrl"). @@ -80,6 +82,11 @@ stop_started_apps(Config) -> App <- lists:reverse(?config(started_apps, Config))]. test_newentry(Config) -> + majority(fun test_newentry_/1, Config). + +test_newentry_({cleanup, Config}) -> + restart_exometer_core(); +test_newentry_(Config) -> {ok, Info} = start_logger_and_reporter(test_udp, Config), Tab = ets_tab(Info), [] = ets:tab2list(Tab), @@ -91,6 +98,11 @@ test_newentry(Config) -> ok. test_subscribe(Config) -> + majority(fun test_subscribe_/1, Config). + +test_subscribe_({cleanup, _Config}) -> + restart_exometer_core(); +test_subscribe_(Config) -> ok = exometer:new([c], counter, []), {ok, _Info} = start_logger_and_reporter(test_udp, Config), exometer_report:subscribe(test_udp, [c], value, main, true), @@ -100,6 +112,11 @@ test_subscribe(Config) -> ok. test_subscribe_find(Config) -> + majority(fun test_subscribe_find_/1, Config). + +test_subscribe_find_({cleanup, _Config}) -> + restart_exometer_core(); +test_subscribe_find_(Config) -> ok = exometer:new([c,1], counter, []), ok = exometer:new([c,2], counter, []), {ok, Info} = start_logger_and_reporter(test_udp, Config), @@ -114,6 +131,11 @@ test_subscribe_find(Config) -> ok. test_subscribe_select(Config) -> + majority(fun test_subscribe_select_/1, Config). + +test_subscribe_select_({cleanup, _Config}) -> + restart_exometer_core(); +test_subscribe_select_(Config) -> ok = exometer:new([c,1], counter, []), ok = exometer:new([c,2], counter, []), ok = exometer:new([c,3], counter, []), @@ -168,7 +190,7 @@ check_logger_msg() -> {logger_got, Data} -> ct:log("logger_got: ~p~n", [Data]), Data - after 1000 -> + after 5000 -> error(logger_ack_timeout) end. @@ -208,3 +230,7 @@ logger_init_output(Pid) -> logger_handle_data(Data, Pid) -> Pid ! {logger_got, Data}, {Data, Pid}. + +restart_exometer_core() -> + application:stop(exometer_core), + application:start(exometer_core). diff --git a/test/exometer_test_udp_reporter.erl b/test/exometer_test_udp_reporter.erl index d0e673f..b6494f8 100644 --- a/test/exometer_test_udp_reporter.erl +++ b/test/exometer_test_udp_reporter.erl @@ -131,7 +131,7 @@ input_loop(Socket, Parent) -> {plugin_active, Active} -> ct:log("{plugin_active, ~p}", [Active]), inet:setopts(Socket, [{active, Active}]) - after 1000 -> + after 5000 -> io:fwrite(user, "input_loop timeout~n", []) end of check -> diff --git a/test/exometer_test_util.erl b/test/exometer_test_util.erl index 7c8baa4..766c2cd 100644 --- a/test/exometer_test_util.erl +++ b/test/exometer_test_util.erl @@ -1,6 +1,7 @@ -module(exometer_test_util). -export([ensure_all_started/1, + majority/2, majority/3]). %% This implementation is originally from Basho's Webmachine. On @@ -34,6 +35,9 @@ ensure_all_started(App, Apps0) -> ensure_all_started(App, [BaseApp|Apps]) end. +majority(F, Cfg) -> + majority(5, F, Cfg). + %% Run test N times. Success if a majority of the tests succeed. %% Cleanup between runs done by calling F({cleanup, Config}) %% Returns 'ok' or {error, Info}. From 564c18f4a1869faf29d431501b3a2d9a9a60b02d Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 17 Apr 2015 20:10:25 +0200 Subject: [PATCH 38/40] try to avoid addrinuse in tests --- test/exometer_report_SUITE.erl | 10 +++++++++- test/exometer_test_udp_reporter.erl | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/test/exometer_report_SUITE.erl b/test/exometer_report_SUITE.erl index 6306ab5..178aa76 100644 --- a/test/exometer_report_SUITE.erl +++ b/test/exometer_report_SUITE.erl @@ -161,7 +161,7 @@ test_logger_flow_control_2(Config) -> start_logger_and_reporter(Reporter, Config) -> Port = get_port(Config), - IPO = config(input_port_options, Config, []), + IPO = ensure_reuse(config(input_port_options, Config, [])), ct:log("IPO = ~p~n", [IPO]), Res = exometer_report_logger:new( [{id, ?MODULE}, @@ -185,6 +185,14 @@ start_logger_and_reporter(Reporter, Config) -> {intervals, [{main, manual}]}]), {ok, Info}. +ensure_reuse(Opts) -> + case lists:keyfind(reuseaddr, 1, Opts) of + {_, _} -> + Opts; + false -> + [{reuseaddr, true}|Opts] + end. + check_logger_msg() -> receive {logger_got, Data} -> diff --git a/test/exometer_test_udp_reporter.erl b/test/exometer_test_udp_reporter.erl index b6494f8..9a65d5f 100644 --- a/test/exometer_test_udp_reporter.erl +++ b/test/exometer_test_udp_reporter.erl @@ -110,6 +110,7 @@ logger_init_input({Port, Opts}) -> Parent = self(), {Pid,_} = spawn_monitor( fun() -> + erlang:monitor(process, Parent), {ok, Socket} = gen_udp:open(Port, [binary|Opts]), check_active(Socket, Parent), input_loop(Socket, Parent) @@ -130,7 +131,9 @@ input_loop(Socket, Parent) -> Parent ! {plugin_passive, self()}, check; {plugin_active, Active} -> ct:log("{plugin_active, ~p}", [Active]), - inet:setopts(Socket, [{active, Active}]) + inet:setopts(Socket, [{active, Active}]); + {'DOWN', _, process, Parent, _} -> + exit(normal) after 5000 -> io:fwrite(user, "input_loop timeout~n", []) end of From 8ef4abe81338abeeded307800cb1c314c062fc43 Mon Sep 17 00:00:00 2001 From: Tino Breddin Date: Mon, 20 Apr 2015 12:37:19 +0200 Subject: [PATCH 39/40] allow adjustment of majority runs from command-line e.g. `CT_MAJORITY_COUNT=10 make` --- test/exometer_SUITE.erl | 2 +- test/exometer_report_SUITE.erl | 4 ++-- test/exometer_test_util.erl | 27 +++++++++++++++++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl index b32f195..2ecb276 100644 --- a/test/exometer_SUITE.erl +++ b/test/exometer_SUITE.erl @@ -280,7 +280,7 @@ test_std_histogram(_Config) -> test_slot_histogram(Config) -> C = [?MODULE, hist, ?LINE], - ok = majority(fun test_slot_histogram_/1, [{metric_name, C}|Config]). + majority(fun test_slot_histogram_/1, [{metric_name, C}|Config]). test_slot_histogram_({cleanup, Config}) -> C = ?config(metric_name, Config), diff --git a/test/exometer_report_SUITE.erl b/test/exometer_report_SUITE.erl index 178aa76..d9b577c 100644 --- a/test/exometer_report_SUITE.erl +++ b/test/exometer_report_SUITE.erl @@ -154,10 +154,10 @@ test_subscribe_select_(Config) -> ok. test_logger_flow_control(Config) -> - ok = test_subscribe_find([{input_port_options, [{active, false}]}|Config]). + test_subscribe_find([{input_port_options, [{active, false}]}|Config]). test_logger_flow_control_2(Config) -> - ok = test_subscribe_find([{input_port_options, [{active, once}]}|Config]). + test_subscribe_find([{input_port_options, [{active, once}]}|Config]). start_logger_and_reporter(Reporter, Config) -> Port = get_port(Config), diff --git a/test/exometer_test_util.erl b/test/exometer_test_util.erl index 766c2cd..fdc57cb 100644 --- a/test/exometer_test_util.erl +++ b/test/exometer_test_util.erl @@ -4,6 +4,9 @@ majority/2, majority/3]). +-define(MAJORITY_COUNT_ENV, "CT_MAJORITY_COUNT"). +-define(DEFAULT_MAJORITY_COUNT, 5). + %% This implementation is originally from Basho's Webmachine. On %% older versions of Erlang, we don't have %% application:ensure_all_started, so we use this wrapper function to @@ -36,7 +39,18 @@ ensure_all_started(App, Apps0) -> end. majority(F, Cfg) -> - majority(5, F, Cfg). + case os:getenv(?MAJORITY_COUNT_ENV) of + false -> + majority(?DEFAULT_MAJORITY_COUNT, F, Cfg); + Count -> + case catch erlang:list_to_integer(Count) of + C when is_integer(C) -> + majority(C, F, Cfg); + _ -> + ct:pal("Invalid value for '~s' given", [?MAJORITY_COUNT_ENV]), + majority(?DEFAULT_MAJORITY_COUNT, F, Cfg) + end + end. %% Run test N times. Success if a majority of the tests succeed. %% Cleanup between runs done by calling F({cleanup, Config}) @@ -46,13 +60,14 @@ majority(N, F, Cfg) -> majority(N, F, Cfg, []). majority(0, _, _, Hist) -> - Failed = [1 || {caught,_,_} <- Hist], - ct:log("majority(): Failed = ~p, Hist=~p~n", [Failed, Hist]), - case {length(Failed), length(Hist)} of + Failed = length([1 || {caught,_,_} <- Hist]), + LogMsg = lists:flatten(io_lib:format("majority: Failed = ~p, Hist = ~p", [Failed, Hist])), + ct:pal(LogMsg), + case {Failed, length(Hist)} of {Lf, L} when Lf >= L div 2 -> - {error, {too_many_failures, Hist}}; + ct:fail({error, {too_many_failures, Hist}}); _ -> - ok + {comment, LogMsg} end; majority(N, F, Cfg, Hist) when N > 0 -> Res = try F(Cfg) From 371001ae5b1ee43ce0b447ffc83970c4cf75e4dc Mon Sep 17 00:00:00 2001 From: Tino Breddin Date: Mon, 20 Apr 2015 13:15:34 +0200 Subject: [PATCH 40/40] fix probe process aliveness checking in tests --- src/exometer_probe.erl | 3 ++- test/exometer_error_SUITE.erl | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/exometer_probe.erl b/src/exometer_probe.erl index 2cfeb67..c846aff 100644 --- a/src/exometer_probe.erl +++ b/src/exometer_probe.erl @@ -583,10 +583,11 @@ default_restart() -> behaviour() -> entry. +delete(_Name, _Type, undefined) -> + ok; delete(_Name, _Type, Pid) when is_pid(Pid) -> exometer_proc:cast(Pid, delete). - get_value(_Name, _Type, Pid) when is_pid(Pid) -> exometer_proc:call(Pid, {get_value, default}). diff --git a/test/exometer_error_SUITE.erl b/test/exometer_error_SUITE.erl index 54d1ea6..d3a50f8 100644 --- a/test/exometer_error_SUITE.erl +++ b/test/exometer_error_SUITE.erl @@ -133,7 +133,7 @@ killed_probe_restarts(M) -> Pid = exometer:info(M, ref), ct:log("Pid = ~p~n", [Pid]), exit(Pid, kill), - ok = await_death(Pid), + await_death(Pid), NewPid = exometer:info(M, ref), ct:log("NewPid = ~p~n", [NewPid]), enabled = exometer:info(M, status), @@ -143,7 +143,7 @@ killed_probe_disabled(M) -> Pid = exometer:info(M, ref), ct:log("Pid = ~p~n", [Pid]), exit(Pid, kill), - ok = await_death(Pid), + await_death(Pid), undefined = exometer:info(M, ref), ct:log("Ref = undefined~n", []), disabled = exometer:info(M, status), @@ -153,7 +153,7 @@ killed_probe_deleted(M) -> Pid = exometer:info(M, ref), ct:log("Pid = ~p~n", [Pid]), exit(Pid, kill), - ok = await_death(Pid), + await_death(Pid), ct:log("Ets = ~p~n", [[{T,ets:tab2list(T)} || T <- exometer_util:tables()]]), {error, not_found} = exometer:get_value(M), @@ -165,7 +165,8 @@ await_death(Pid) -> await_death(Pid, Ref), %% Now ping exometer_admin twice to give it time to handle the DOWN msg sys:get_status(exometer_admin), - sys:get_status(exometer_admin). + sys:get_status(exometer_admin), + ok. await_death(Pid, Ref) -> case erlang:read_timer(Ref) of @@ -178,8 +179,8 @@ await_death(Pid, Ref) -> await_death(Pid, Ref); false -> erlang:cancel_timer(Ref), - _ = sys:get_status(exometer_admin), - _ = sys:get_status(exometer_admin), + sys:get_status(exometer_admin), + sys:get_status(exometer_admin), ok end end.