Current section
Files
Jump to
Current section
Files
src/exometer_util.erl
%% -------------------------------------------------------------------
%%
%% 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 utility functions.
%% @end
-module(exometer_util).
-export(
[
timestamp/0,
timestamp_to_datetime/1,
get_opt/3,
get_env/2,
tables/0,
table/0,
get_statistics/3,
get_statistics2/4,
pick_items/2,
histogram/1,
histogram/2,
drop_duplicates/1,
report_type/3,
get_datapoints/1,
set_call_count/2, set_call_count/3,
get_status/1,
set_status/2,
set_event_flag/2,
clear_event_flag/2,
test_event_flag/2
]).
-export_type([timestamp/0]).
-include("exometer.hrl").
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-type timestamp() :: non_neg_integer().
-spec timestamp() -> timestamp().
%% @doc Generate a millisecond-resolution timestamp.
%%
%% This timestamp format is used e.g. by the `exometer_slide' and
%% `exometer_histogram' implementations.
%% @end
timestamp() ->
%% Invented epoc is {1258,0,0}, or 2009-11-12, 4:26:40
%% Millisecond resolution
{MS,S,US} = os:timestamp(),
(MS-1258)*1000000000 + S*1000 + US div 1000.
-spec timestamp_to_datetime(timestamp()) ->
{calendar:datetime(), non_neg_integer()}.
%% @doc Convert timestamp to a regular datetime.
%%
%% The timestamp is expected
timestamp_to_datetime(TS) when TS >= 0 ->
%% Our internal timestamps are relative to Now = {1258,0,0}
%% It doesn't really matter much how we construct a now()-like tuple,
%% as long as the weighted sum of the three numbers is correct.
S = TS div 1000,
MS = TS rem 1000,
%% return {Datetime, Milliseconds}
{calendar:now_to_datetime({1258,S,0}), MS}.
get_env(Key, Default) ->
case get_env1(exometer, Key) of
{ok, Value} ->
Value;
_ ->
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
end.
get_opt(K, Opts, Default) ->
case lists:keyfind(K, 1, Opts) of
{_, V} -> V;
false ->
if is_function(Default,0) -> Default();
true -> Default
end
end.
tables() ->
[table(S) || S <- lists:seq(1,erlang:system_info(schedulers))].
table() ->
table(erlang:system_info(scheduler_id)).
table(1) -> exometer_1;
table(2) -> exometer_2;
table(3) -> exometer_3;
table(4) -> exometer_4;
table(5) -> exometer_5;
table(6) -> exometer_6;
table(7) -> exometer_7;
table(8) -> exometer_8;
table(9) -> exometer_9;
table(10) -> exometer_10;
table(11) -> exometer_11;
table(12) -> exometer_12;
table(13) -> exometer_13;
table(14) -> exometer_14;
table(15) -> exometer_15;
table(16) -> exometer_16;
table(17) -> exometer_17;
table(18) -> exometer_18;
table(19) -> exometer_19;
table(20) -> exometer_20;
table(21) -> exometer_21;
table(22) -> exometer_22;
table(23) -> exometer_23;
table(24) -> exometer_24;
table(25) -> exometer_25;
table(26) -> exometer_26;
table(27) -> exometer_27;
table(28) -> exometer_28;
table(29) -> exometer_29;
table(30) -> exometer_30;
table(31) -> exometer_31;
table(32) -> exometer_32;
table(33) -> exometer_33;
table(34) -> exometer_34;
table(35) -> exometer_35;
table(36) -> exometer_36;
table(37) -> exometer_37;
table(38) -> exometer_38;
table(39) -> exometer_39;
table(40) -> exometer_40;
table(41) -> exometer_41;
table(42) -> exometer_42;
table(43) -> exometer_43;
table(44) -> exometer_44;
table(45) -> exometer_45;
table(46) -> exometer_46;
table(47) -> exometer_47;
table(48) -> exometer_48;
table(49) -> exometer_49;
table(50) -> exometer_50;
table(51) -> exometer_51;
table(52) -> exometer_52;
table(53) -> exometer_53;
table(54) -> exometer_54;
table(55) -> exometer_55;
table(56) -> exometer_56;
table(57) -> exometer_57;
table(58) -> exometer_58;
table(59) -> exometer_59;
table(60) -> exometer_60;
table(61) -> exometer_61;
table(62) -> exometer_62;
table(63) -> exometer_63;
table(64) -> exometer_64;
table(N) when is_integer(N), N > 20 ->
list_to_atom("exometer_" ++ integer_to_list(N)).
%% @doc
%% `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.
%% @end
-spec drop_duplicates(List0 :: [tuple()]) -> [tuple()].
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;
(_, Acc) ->
Acc
end, [], List0),
lists:reverse(List1);
drop_duplicates(Any) ->
Any.
histogram(Values) ->
Sorted = lists:sort(Values),
Len = length(Sorted),
Total = lists:foldl(fun(I,Acc) -> Acc + I end, 0, Sorted),
get_statistics(Len, Total, Sorted).
histogram(Values, default) ->
histogram(Values);
histogram(Values, DataPoints) ->
H = histogram(Values),
[DP || {K,_} = DP <- H,
lists:member(K, DataPoints)].
-spec get_statistics(Length::non_neg_integer(),
Total::non_neg_integer(),
Sorted::list()) -> [{atom(), number()}].
%% @doc Calculate statistics from a sorted list of values.
%%
%% This function assumes that you have already sorted the list, and
%% now the number and sum of the elements in the list.
%%
%% The stats calculated are min, max, mean, median and the 50th,
%% 75th, 90th, 95th, 99th, and 99.9th percentiles (note that the
%% 99.9th percentile is labeled 999).
%%
%% This function is similar to `bear:get_statistics_subset/2'.
%% `mean' refers to the arithmetic mean.
%%
%% Fulpatchad med min/max av Magnus Feuer.
%% @end
get_statistics(_, _, []) ->
[];
get_statistics(L, Total, Sorted) ->
get_statistics2(L, Sorted, Total, Total / L).
%%
%% Special case when we get called with an empty histogram.
get_statistics2(_L, [], _Total, _Mean) ->
[];
%% Special case when we get called from
%% exometer_histogram:get_value_int() with just
%% a nil min/max pair.
get_statistics2(_L, [0,0], _Total, _Mean) ->
[];
get_statistics2(L, Sorted, Total, Mean) ->
P50 = perc(0.5, L),
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)].
-spec pick_items([number()], [{atom() | integer(), integer()}]) ->
[{atom(), number()}].
%% @doc Pick values from specified positions in a sorted list of numbers.
%%
%% This function is used to extract datapoints (usually percentiles) from
%% a sorted list of values. `Items' is a list of `{Datapoint, Position}'
%% entries.
%% @end
pick_items(Vals, Items) ->
pick_items(Vals, 1, Items).
pick_items([H|_] = L, P, [{Tag,P}|Ps]) ->
[{Tag,H} | pick_items(L, P, Ps)];
pick_items([_|T], P, Ps) ->
pick_items(T, P+1, Ps);
pick_items([], _, Ps) ->
[{Tag, 0.0} || {Tag,_} <- Ps].
perc(P, Len) when P > 1.0 ->
round((P / 10) * Len);
perc(P, Len) ->
round(P * Len).
report_type(Key, Extra, TypeMap) when is_list(Extra) ->
case lists:keyfind(report_type, 1, Extra) of
{_, Type} -> {ok, Type};
false -> check_type(Key, TypeMap)
end;
report_type(Key, _, TypeMap) ->
check_type(Key, TypeMap).
check_type(K, [{K, Type}|_]) ->
{ok, Type};
check_type(K, [{KPat, Type}|T]) ->
case key_match(KPat, K) of
true -> {ok, Type};
false -> check_type(K, T)
end;
check_type(_, []) ->
error.
key_match([H|T], [H|T1]) ->
key_match(T, T1);
key_match(['_'|T], [_|T1]) ->
key_match(T, T1);
key_match([], []) -> true;
key_match('_', _) -> true;
key_match(_, _) -> false.
get_datapoints(#exometer_entry{module = exometer,
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}) ->
M:get_datapoints(Name, Type, Ref);
get_datapoints(#exometer_entry{behaviour = probe,
name = Name, type = Type, ref = Ref}) ->
exometer_probe:get_datapoints(Name, Type, Ref).
set_call_count({M, F}, Bool) ->
set_call_count(M, F, Bool).
set_call_count(M, F, Bool) when is_atom(M), is_atom(F), is_boolean(Bool) ->
erlang:trace_pattern({M, F, 0}, Bool, [call_count]).
get_status(enabled) -> enabled;
get_status(disabled) -> disabled;
get_status(St) when is_integer(St) ->
if St band 2#1 == 1 ->
enabled;
true ->
disabled
end.
set_status(enabled , enabled ) -> 1;
set_status(enabled , disabled) -> 1;
set_status(enabled , St ) -> St bor 2#1;
set_status(disabled, enabled ) -> 0;
set_status(disabled, disabled) -> 0;
set_status(disabled, St ) -> St band 2#11111110.
set_event_flag(update, St) when is_integer(St) ->
St bor 2#10;
set_event_flag(update, enabled ) -> 2#11;
set_event_flag(update, disabled) -> 2#10.
clear_event_flag(update, St) when is_integer(St) ->
St band 2#11111101;
clear_event_flag(update, enabled ) -> 1;
clear_event_flag(update, disabled) -> 0.
test_event_flag(update, St) when St band 2#10 =:= 2#10 -> true;
test_event_flag(update, _) -> false.
%% EUnit tests
-ifdef(TEST).
key_match_test() ->
{ok,yes} = report_type([a,b,c], [], [{'_',yes}]),
{ok,yes} = report_type([a,b,c], [], [{[a,b], no},
{[a,b,c], yes},
{[a,b,c], no}]), % match on first
{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], [{report_type,yes}], [{[a,b,c], no}]),
ok.
-endif.