diff --git a/bin/rebar3 b/bin/rebar3 index 3737e0c..a5f263e 100755 Binary files a/bin/rebar3 and b/bin/rebar3 differ diff --git a/include/statsderl.hrl b/include/statsderl.hrl index dea2fad..02fcbd0 100644 --- a/include/statsderl.hrl +++ b/include/statsderl.hrl @@ -17,8 +17,9 @@ -type base_key() :: base_key_part() | [base_key_part()]. -type base_key_part() :: hostname | name | sname | undefined | iodata(). -type key() :: iodata(). --type op_code() :: decrement | gauge | gauge_decrement | gauge_increment | - increment | timing. +-type op() :: term(). +-type op_code() :: counter | gauge | gauge_decrement | gauge_increment | + timing | timing_now | timing_now_us. -type sample_rate() :: number(). -type value() :: number(). diff --git a/rebar.config b/rebar.config index 0464f9c..32a8b4f 100644 --- a/rebar.config +++ b/rebar.config @@ -1,7 +1,9 @@ {cover_export_enabled, true}. {cover_excl_mods, [ + statsderl_debug, statsderl_profile, statsderl_tests, + statsderl_transform, statsderl_utils_tests ]}. {coveralls_coverdata, "_build/test/cover/eunit.coverdata"}. @@ -9,7 +11,9 @@ {deps, [ {granderl, ".*", - {git, "https://github.com/tokenrove/granderl.git", {tag, "v0.1.4"}}} + {git, "https://github.com/tokenrove/granderl.git", {tag, "v0.1.4"}}}, + {parse_trans, ".*", + {git, "https://github.com/uwiger/parse_trans.git", {tag, "3.0.0"}}} ]}. {edoc_opts, [ @@ -22,6 +26,8 @@ {title, "statsderl"} ]}. +{erl_first_files, ["src/statsderl_transform.erl"]}. + {erl_opts, [ debug_info, {platform_define, "19", 'UDP_HEADER'} diff --git a/rebar.lock b/rebar.lock index 09a02b8..6c45557 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,4 +1,8 @@ [{<<"granderl">>, {git,"https://github.com/tokenrove/granderl.git", {ref,"b44725cdd3ae6d2d4170e0d72ebb2fcad98d5ad2"}}, + 0}, + {<<"parse_trans">>, + {git,"https://github.com/uwiger/parse_trans.git", + {ref,"6f3645afb43c7c57d61b54ef59aecab288ce1013"}}, 0}]. diff --git a/src/statsderl.erl b/src/statsderl.erl index 581c3fa..330486e 100644 --- a/src/statsderl.erl +++ b/src/statsderl.erl @@ -22,108 +22,62 @@ -spec counter(key(), value(), sample_rate()) -> ok. -counter(Key, Value, SampleRate) -> - sample(counter, Key, Value, SampleRate). +counter(Key, Value, Rate) -> + statsderl_pool:sample(Rate, {counter, Key, Value, Rate}). -spec decrement(key(), value(), sample_rate()) -> ok. -decrement(Key, Value, SampleRate) when Value >= 0 -> - sample(counter, Key, -Value, SampleRate). +decrement(Key, Value, Rate) when Value >= 0 -> + statsderl_pool:sample(Rate, {counter, Key, -Value, Rate}). -spec gauge(key(), value(), sample_rate()) -> ok. -gauge(Key, Value, SampleRate) when Value >= 0 -> - sample(gauge, Key, Value, SampleRate). +gauge(Key, Value, Rate) when Value >= 0 -> + statsderl_pool:sample(Rate, {gauge, Key, Value}). -spec gauge_decrement(key(), value(), sample_rate()) -> ok. -gauge_decrement(Key, Value, SampleRate) when Value >= 0 -> - sample(gauge_decrement, Key, Value, SampleRate). +gauge_decrement(Key, Value, Rate) when Value >= 0 -> + statsderl_pool:sample(Rate, {gauge_decrement, Key, Value}). -spec gauge_increment(key(), value(), sample_rate()) -> ok. -gauge_increment(Key, Value, SampleRate) when Value >= 0 -> - sample(gauge_increment, Key, Value, SampleRate). +gauge_increment(Key, Value, Rate) when Value >= 0 -> + statsderl_pool:sample(Rate, {gauge_increment, Key, Value}). -spec increment(key(), value(), sample_rate()) -> ok. -increment(Key, Value, SampleRate) when Value >= 0 -> - sample(counter, Key, Value, SampleRate). +increment(Key, Value, Rate) when Value >= 0 -> + statsderl_pool:sample(Rate, {counter, Key, Value, Rate}). -spec timing(key(), value(), sample_rate()) -> ok. -timing(Key, Value, SampleRate) -> - sample(timing, Key, Value, SampleRate). +timing(Key, Value, Rate) -> + statsderl_pool:sample(Rate, {timing, Key, Value}). -spec timing_fun(key(), fun(), sample_rate()) -> ok. -timing_fun(Key, Fun, SampleRate) -> +timing_fun(Key, Fun, Rate) -> Timestamp = statsderl_utils:timestamp(), Result = Fun(), - timing_now(Key, Timestamp, SampleRate), + timing_now(Key, Timestamp, Rate), Result. -spec timing_now(key(), erlang:timestamp(), sample_rate()) -> ok. -timing_now(Key, Timestamp, SampleRate) -> - sample(timing_now, Key, Timestamp, SampleRate). +timing_now(Key, Timestamp, Rate) -> + statsderl_pool:sample(Rate, {timing_now, Key, Timestamp}). -spec timing_now_us(key(), erlang:timestamp(), sample_rate()) -> ok. -timing_now_us(Key, Timestamp, SampleRate) -> - sample(timing_now_us, Key, Timestamp, SampleRate). - -%% private -cast(OpCode, Key, Value, SampleRate, ServerName) -> - Packet = statsderl_protocol:encode(OpCode, Key, Value, SampleRate), - send(ServerName, {cast, Packet}). - -operation(OpCode, Key, Value, SampleRate) -> - ServerName = statsderl_utils:random_server(), - operation(OpCode, Key, Value, SampleRate, ServerName). - -operation(timing_now, Key, Value, SampleRate, ServerName) -> - cast(timing, Key, timing_now(Value), SampleRate, ServerName); -operation(timing_now_us, Key, Value, SampleRate, ServerName) -> - cast(timing, Key, timing_now_us(Value), SampleRate, ServerName); -operation(OpCode, Key, Value, SampleRate, ServerName) -> - cast(OpCode, Key, Value, SampleRate, ServerName). - -sample(OpCode, Key, Value, 1) -> - operation(OpCode, Key, Value, 1); -sample(OpCode, Key, Value, 1.0) -> - operation(OpCode, Key, Value, 1); -sample(OpCode, Key, Value, SampleRate) -> - Rand = statsderl_utils:random(?MAX_UNSIGNED_INT_32), - case Rand =< SampleRate * ?MAX_UNSIGNED_INT_32 of - true -> - N = Rand rem ?POOL_SIZE + 1, - ServerName = statsderl_utils:server_name(N), - operation(OpCode, Key, Value, SampleRate, ServerName); - false -> - ok - end. - -send(ServerName, Msg) -> - case whereis(ServerName) of - undefined -> - ok; - Pid -> - Pid ! Msg - end. - -timing_now(Timestamp) -> - timing_now_us(Timestamp) div 1000. - -timing_now_us(Timestamp) -> - Timestamp2 = statsderl_utils:timestamp(), - timer:now_diff(Timestamp2, Timestamp). +timing_now_us(Key, Timestamp, Rate) -> + statsderl_pool:sample(Rate, {timing_now_us, Key, Timestamp}). diff --git a/src/statsderl_debug.erl b/src/statsderl_debug.erl new file mode 100644 index 0000000..a721dd2 --- /dev/null +++ b/src/statsderl_debug.erl @@ -0,0 +1,24 @@ +-module(statsderl_debug). + +-compile({parse_transform, statsderl_transform}). + +-export([test/0]). + +-spec test() -> ok. + +test() -> + A = <<"my_key">>, + B = 10, + C = 0.666, + + statsderl:counter(["hello", <<"world">>], -1, 0.5), + statsderl:counter(["hello", A], 1, 0.5), + statsderl:counter(["hello", <<"world">>], B, 0.5), + statsderl:counter(["hello", <<"world">>], 1, C), + + statsderl:gauge(["hello", <<"world">>], 1, 0.5), + statsderl:gauge(["hello", A], 1, 0.5), + statsderl:gauge(["hello", <<"world">>], B, 0.5), + statsderl:gauge(["hello", <<"world">>], 1, C), + + ok. diff --git a/src/statsderl_pool.erl b/src/statsderl_pool.erl new file mode 100644 index 0000000..95fe2d2 --- /dev/null +++ b/src/statsderl_pool.erl @@ -0,0 +1,70 @@ +-module(statsderl_pool). +-include("statsderl.hrl"). + +-compile(inline). +-compile({inline_size, 512}). + +-export([ + sample/2, + sample_scaled/2, + server_name/1 +]). + +%% public +-spec sample(sample_rate(), op()) -> ok. + +sample(1, Op) -> + operation(Op); +sample(1.0, Op) -> + operation(Op); +sample(Rate, Op) -> + RateInt = trunc(Rate * ?MAX_UNSIGNED_INT_32), + sample_scaled(RateInt, Op). + +-spec sample_scaled(non_neg_integer(), op()) -> ok. + +sample_scaled(RateInt, Op) -> + Rand = statsderl_utils:random(?MAX_UNSIGNED_INT_32), + case Rand =< RateInt of + true -> + N = Rand rem ?POOL_SIZE + 1, + operation(Op, server_name(N)); + false -> + ok + end. + +-spec server_name(1..4) -> atom(). + +server_name(1) -> statsderl_1; +server_name(2) -> statsderl_2; +server_name(3) -> statsderl_3; +server_name(4) -> statsderl_4. + +%% private +cast({cast, _} = Cast, ServerName) -> + send(ServerName, Cast); +cast(Op, ServerName) -> + send(ServerName, {cast, statsderl_protocol:encode(Op)}). + +operation(Op) -> + operation(Op, random_server()). + +operation({timing_now, Key, Value}, ServerName) -> + Value2 = statsderl_utils:timing_now(Value), + cast({timing, Key, Value2}, ServerName); +operation({timing_now_us, Key, Value}, ServerName) -> + Value2 = statsderl_utils:timing_now_us(Value), + cast({timing, Key, Value2}, ServerName); +operation(Op, ServerName) -> + cast(Op, ServerName). + +random_server() -> + server_name(statsderl_utils:random(?POOL_SIZE)). + +send(ServerName, Msg) -> + case whereis(ServerName) of + undefined -> + ok; + Pid -> + Pid ! Msg + end. diff --git a/src/statsderl_protocol.erl b/src/statsderl_protocol.erl index 01c6697..51983c3 100644 --- a/src/statsderl_protocol.erl +++ b/src/statsderl_protocol.erl @@ -5,22 +5,22 @@ -compile({inline_size, 512}). -export([ - encode/4 + encode/1 ]). %% public --spec encode(op_code(), key(), value(), sample_rate()) -> iodata(). +-spec encode({op_code(), key(), value(), sample_rate()}) -> iodata(). -encode(counter, Key, Value, SampleRate) -> +encode({counter, Key, Value, SampleRate}) -> [Key, <<":">>, format_value(Value), <<"|c">>, format_sample_rate(SampleRate)]; -encode(gauge, Key, Value, _SampleRate) -> +encode({gauge, Key, Value}) -> [Key, <<":">>, format_value(Value), <<"|g">>]; -encode(gauge_decrement, Key, Value, _SampleRate) -> +encode({gauge_decrement, Key, Value}) -> [Key, <<":-">>, format_value(Value), <<"|g">>]; -encode(gauge_increment, Key, Value, _SampleRate) -> +encode({gauge_increment, Key, Value}) -> [Key, <<":+">>, format_value(Value), <<"|g">>]; -encode(timing, Key, Value, _SampleRate) -> +encode({timing, Key, Value}) -> [Key, <<":">>, format_value(Value), <<"|ms">>]. %% private diff --git a/src/statsderl_sup.erl b/src/statsderl_sup.erl index 36e744d..159619a 100644 --- a/src/statsderl_sup.erl +++ b/src/statsderl_sup.erl @@ -28,5 +28,5 @@ init(_Args) -> child_specs(0) -> []; child_specs(N) -> - Name = statsderl_utils:server_name(N), + Name = statsderl_pool:server_name(N), [?CHILD(Name, ?SERVER) | child_specs(N - 1)]. diff --git a/src/statsderl_transform.erl b/src/statsderl_transform.erl new file mode 100644 index 0000000..f0351d1 --- /dev/null +++ b/src/statsderl_transform.erl @@ -0,0 +1,126 @@ +-module(statsderl_transform). +-include("statsderl.hrl"). + +-export([ + parse_transform/2 +]). + +-type forms() :: [erl_parse:abstract_form() | erl_parse:form_info()]. + +%% public +-spec parse_transform(forms(), [compile:option()]) -> forms(). + +parse_transform(Forms, _Options) -> + parse_trans:plain_transform(fun do_transform/1, Forms). + +do_transform({call, _, {_, _, {_, _, ?APP}, Function}, _} = F) -> + replace(safe_normalize(Function), F); +do_transform(_Form) -> + continue. + +%% private +encode(Op) -> + Proto = statsderl_protocol:encode(Op), + binary_to_list(iolist_to_binary(Proto)). + +not_undefined([]) -> + true; +not_undefined([undefined | _]) -> + false; +not_undefined([_ | T]) -> + not_undefined(T). + +op_code(increment) -> + counter; +op_code(decrement) -> + counter; +op_code(Function) -> + Function. + +packet(counter, Key, Value, Rate) -> + Key2 = safe_normalize(Key), + Value2 = safe_normalize(Value), + Rate2 = safe_normalize(Rate), + + case not_undefined([Key2, Value2, Rate2]) of + true -> + encode({counter, Key2, Value2, Rate2}); + false -> + undefined + end; +packet(OpCode, Key, Value, _Rate) -> + Key2 = safe_normalize(Key), + Value2 = safe_normalize(Value), + + case not_undefined([Key2, Value2]) of + true -> + encode({OpCode, Key2, Value2}); + false -> + undefined + end. + +rate_scaled({float, _, RateValue}) -> + trunc(RateValue * ?MAX_UNSIGNED_INT_32); +rate_scaled({integer, _, RateValue}) -> + trunc(RateValue * ?MAX_UNSIGNED_INT_32); +rate_scaled(_) -> + undefined. + +replace(timing_fun, F) -> + F; +replace(Function, {_, _, _, [Key, Value, Rate]} = F) -> + RateScaled = rate_scaled(Rate), + OpCode = op_code(Function), + Value2 = value(Function, Value), + Packet = packet(OpCode, Key, Value2, Rate), + + case {RateScaled, Packet} of + {undefined, undefined} -> + F; + {_, undefined} -> + Tuple = case OpCode of + counter -> + {tuple, 0, [{atom, 0, OpCode}, Key, Value2, Rate]}; + _ -> + {tuple, 0, [{atom, 0, OpCode}, Key, Value2]} + end, + + {call, 0, {remote, 0, + {atom, 0, statsderl_pool}, + {atom, 0, sample_scaled}}, [ + {integer, 0, RateScaled}, + Tuple + ]}; + {undefined, _} -> + Packet2 = erl_syntax:revert(erl_syntax:binary( + [erl_syntax:binary_field(erl_syntax:string(Packet))])), + + {call, 0, {remote, 0, + {atom, 0, statsderl_pool}, + {atom, 0, sample}}, [ + Rate, + {tuple, 0, [{atom, 0, cast}, Packet2]} + ]}; + _ -> + Packet2 = erl_syntax:revert(erl_syntax:binary( + [erl_syntax:binary_field(erl_syntax:string(Packet))])), + + {call, 0, {remote, 0, + {atom, 0, statsderl_pool}, + {atom, 0, sample_scaled}}, [ + {integer, 0, RateScaled}, + {tuple, 0, [{atom, 0, cast}, Packet2]} + ]} + end. + +safe_normalize(AbsTerm) -> + try erl_parse:normalise(AbsTerm) + catch + _:_ -> + undefined + end. + +value(decrement, Value) -> + {op, 0, '-', Value}; +value(_, Value) -> + Value. diff --git a/src/statsderl_utils.erl b/src/statsderl_utils.erl index b325173..0ba1f31 100644 --- a/src/statsderl_utils.erl +++ b/src/statsderl_utils.erl @@ -1,15 +1,18 @@ -module(statsderl_utils). -include("statsderl.hrl"). +-compile(inline). +-compile({inline_size, 512}). + -export([ base_key/1, error_msg/2, getaddrs/1, random/1, random_element/1, - random_server/0, - server_name/1, - timestamp/0 + timestamp/0, + timing_now/1, + timing_now_us/1 ]). %% public @@ -70,22 +73,20 @@ random_element([_|_] = List) -> Index = random(tuple_size(T)), element(Index, T). --spec random_server() -> atom(). +-spec timestamp() -> erlang:timestamp(). -random_server() -> - server_name(random(?POOL_SIZE)). +timestamp() -> + os:timestamp(). --spec server_name(pos_integer()) -> atom(). +-spec timing_now(erlang:timestamp()) -> non_neg_integer(). -server_name(1) -> statsderl_1; -server_name(2) -> statsderl_2; -server_name(3) -> statsderl_3; -server_name(4) -> statsderl_4. +timing_now(Timestamp) -> + timing_now_us(Timestamp) div 1000. --spec timestamp() -> erlang:timestamp(). +-spec timing_now_us(erlang:timestamp()) -> non_neg_integer(). -timestamp() -> - os:timestamp(). +timing_now_us(Timestamp) -> + timer:now_diff(statsderl_utils:timestamp(), Timestamp). %% private hostname() -> diff --git a/test/statsderl_tests.erl b/test/statsderl_tests.erl index c36d5b7..b82ffff 100644 --- a/test/statsderl_tests.erl +++ b/test/statsderl_tests.erl @@ -2,6 +2,8 @@ -include_lib("statsderl/include/statsderl.hrl"). -include_lib("eunit/include/eunit.hrl"). +-compile({parse_transform, statsderl_transform}). + -spec test() -> ok. statsderl_base_key_test() ->