diff --git a/.travis.yml b/.travis.yml index fefa923..b8e5d96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ language: erlang notifications: email: false otp_release: + - 19.1 - 18.3 - 17.5 script: "make travis" diff --git a/README.md b/README.md index 42138b7..0962b35 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ High Performance Erlang StatsD Client ### Features * Performance optimized +* Parse transform ## API Function Index @@ -74,6 +75,13 @@ High Performance Erlang StatsD Client +## Parse Transform + +`statsderl_transform` is used to pre-compute (at compile time) the scaled sample rate and packet. + +```erlang +-compile({parse_transform, statsderl_transform}). +``` ## Examples ```erlang @@ -120,10 +128,6 @@ ok. make test ``` -### TODOs - -* parse transform for type conversion - ## License ```license 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..e4a3a40 100644 --- a/include/statsderl.hrl +++ b/include/statsderl.hrl @@ -17,8 +17,13 @@ -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 operation() :: {cast, iodata()} | + {counter, key(), value(), sample_rate()} | + {gauge, key(), value()} | + {gauge_decrement, key(), value()} | + {gauge_increment, key(), value()} | + {timing, key(), value()} | + {timing_now, key(), erlang:timestamp()} | + {timing_now_us, key(), erlang:timestamp()}. -type sample_rate() :: number(). -type value() :: number(). - diff --git a/rebar.config b/rebar.config index 0464f9c..ab8ae89 100644 --- a/rebar.config +++ b/rebar.config @@ -2,6 +2,8 @@ {cover_excl_mods, [ statsderl_profile, statsderl_tests, + statsderl_transform, + statsderl_transform_tests, 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, [ 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_app.erl b/src/statsderl_app.erl index b48f243..43113b5 100644 --- a/src/statsderl_app.erl +++ b/src/statsderl_app.erl @@ -13,23 +13,27 @@ ]). %% public --spec start() -> {ok, [atom()]} | {error, term()}. +-spec start() -> + {ok, [atom()]} | {error, term()}. start() -> application:ensure_all_started(?APP). --spec stop() -> ok | {error, {not_started, ?APP}}. +-spec stop() -> + ok | {error, {not_started, ?APP}}. stop() -> application:stop(?APP). %% application callbacks --spec start(application:start_type(), term()) -> {ok, pid()}. +-spec start(application:start_type(), term()) -> + {ok, pid()}. start(_StartType, _StartArgs) -> statsderl_sup:start_link(). --spec stop(term()) -> ok. +-spec stop(term()) -> + ok. stop(_State) -> ok. diff --git a/src/statsderl_pool.erl b/src/statsderl_pool.erl new file mode 100644 index 0000000..75d9207 --- /dev/null +++ b/src/statsderl_pool.erl @@ -0,0 +1,73 @@ +-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(), operation()) -> + ok. + +sample(1, Operation) -> + operation(Operation); +sample(1.0, Operation) -> + operation(Operation); +sample(Rate, Operation) -> + RateInt = trunc(Rate * ?MAX_UNSIGNED_INT_32), + sample_scaled(RateInt, Operation). + +-spec sample_scaled(non_neg_integer(), operation()) -> + ok. + +sample_scaled(RateInt, Operation) -> + Rand = granderl:uniform(?MAX_UNSIGNED_INT_32), + case Rand =< RateInt of + true -> + N = Rand rem ?POOL_SIZE + 1, + operation(Operation, 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(Operation, ServerName) -> + send(ServerName, {cast, statsderl_protocol:encode(Operation)}). + +operation(Operation) -> + operation(Operation, 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(Operation, ServerName) -> + cast(Operation, ServerName). + +random_server() -> + server_name(granderl:uniform(?POOL_SIZE)). + +send(ServerName, Msg) -> + try + ServerName ! Msg + catch + _:_ -> + ok + end. diff --git a/src/statsderl_protocol.erl b/src/statsderl_protocol.erl index 01c6697..5561a63 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(operation()) -> 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_server.erl b/src/statsderl_server.erl index 5869576..3e29443 100644 --- a/src/statsderl_server.erl +++ b/src/statsderl_server.erl @@ -15,7 +15,8 @@ }). %% public --spec init(pid(), atom()) -> no_return(). +-spec init(pid(), atom()) -> + no_return(). init(Parent, Name) -> BaseKey = ?ENV(?ENV_BASEKEY, ?DEFAULT_BASEKEY), @@ -40,29 +41,27 @@ init(Parent, Name) -> exit(Reason) end. --spec start_link(atom()) -> {ok, pid()}. +-spec start_link(atom()) -> + {ok, pid()}. start_link(Name) -> proc_lib:start_link(?MODULE, init, [self(), Name]). %% private -handle_msg({cast, Packet}, #state { +loop(#state { header = Header, socket = Socket } = State) -> - statsderl_udp:send(Socket, Header, Packet), - {ok, State}; -handle_msg({inet_reply, _Socket, ok}, State) -> - {ok, State}; -handle_msg({inet_reply, _Socket, {error, Reason}}, State) -> - statsderl_utils:error_msg("inet_reply error: ~p~n", [Reason]), - {ok, State}. - -loop(State) -> - receive Msg -> - {ok, State2} = handle_msg(Msg, State), - loop(State2) + receive + {cast, Packet} -> + erlang:port_command(Socket, [Header, Packet]), + loop(State); + {inet_reply, _Socket, ok} -> + loop(State); + {inet_reply, _Socket, {error, Reason}} -> + statsderl_utils:error_msg("inet_reply error: ~p~n", [Reason]), + loop(State) end. udp_header(Hostname, Port, BaseKey) -> diff --git a/src/statsderl_sup.erl b/src/statsderl_sup.erl index 36e744d..ac1921d 100644 --- a/src/statsderl_sup.erl +++ b/src/statsderl_sup.erl @@ -13,13 +13,15 @@ %% public --spec start_link() -> {ok, pid()}. +-spec start_link() -> + {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor callbacks --spec init([]) -> {ok, {{one_for_one, 5, 10}, [supervisor:child_spec()]}}. +-spec init([]) -> + {ok, {{one_for_one, 5, 10}, [supervisor:child_spec()]}}. init(_Args) -> {ok, {{one_for_one, 5, 10}, child_specs(?POOL_SIZE)}}. @@ -28,5 +30,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..2914b07 --- /dev/null +++ b/src/statsderl_transform.erl @@ -0,0 +1,128 @@ +-module(statsderl_transform). +-include("statsderl.hrl"). + +-export([ + parse_transform/2 +]). + +-define(ATOM(Atom), {atom, 0, Atom}). +-define(BIN_ELEMENT(String), {bin_element, 0, String, default, default}). +-define(BINARY(String), {bin, 0, [?BIN_ELEMENT(?STRING(String))]}). +-define(INTEGER(Int), {integer, 0, Int}). +-define(OP_NEGATIVE(X), {op, 0, '-', X}). +-define(STRING(String), {string, 0, String}). +-define(TUPLE(List), {tuple, 0, List}). + +-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 +call(Function, Arg1, Arg2) -> + {call, 0, {remote, 0, + {atom, 0, statsderl_pool}, + {atom, 0, Function}}, [Arg1, Arg2] + }. + +encode(Operation) -> + Encoded = statsderl_protocol:encode(Operation), + binary_to_list(iolist_to_binary(Encoded)). + +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} -> + case OpCode of + counter -> + sample_scaled(RateScaled, + ?TUPLE([?ATOM(OpCode), Key, Value2, Rate])); + _ -> + sample_scaled(RateScaled, + ?TUPLE([?ATOM(OpCode), Key, Value2])) + end; + {undefined, _} -> + sample(Rate, + ?TUPLE([?ATOM(cast), ?BINARY(Packet)])); + _ -> + sample_scaled(RateScaled, + ?TUPLE([?ATOM(cast), ?BINARY(Packet)])) + end. + +sample(Rate, Arguments) -> + call(sample, Rate, Arguments). + +sample_scaled(RateScaled, Arguments) -> + call(sample_scaled, ?INTEGER(RateScaled), Arguments). + +safe_normalize(AbsTerm) -> + try erl_parse:normalise(AbsTerm) + catch + _:_ -> + undefined + end. + +value(decrement, Value) -> + ?OP_NEGATIVE(Value); +value(_, Value) -> + Value. diff --git a/src/statsderl_udp.erl b/src/statsderl_udp.erl index 73f0bcf..6edd1a0 100644 --- a/src/statsderl_udp.erl +++ b/src/statsderl_udp.erl @@ -5,15 +5,15 @@ -compile({inline_size, 512}). -export([ - header/2, - send/3 + header/2 ]). -define(INET_AF_INET, 1). -define(INT16(X), [((X) bsr 8) band 16#ff, (X) band 16#ff]). %% public --spec header(inet:ip_address(), inet:port_number()) -> iodata(). +-spec header(inet:ip_address(), inet:port_number()) -> + iodata(). -ifdef(UDP_HEADER). @@ -27,19 +27,6 @@ header(IP, Port) -> -endif. --spec send(inet:socket(), iodata(), iodata()) -> - ok | {error, term()}. - -send(Socket, Header, Data) -> - try - true = erlang:port_command(Socket, [Header, Data]), - ok - catch - Error:Reason -> - statsderl_utils:error_msg("port_command ~p: ~p~n", [Error, Reason]), - {error, {Error, Reason}} - end. - %% private ip4_to_bytes({A, B, C, D}) -> [A band 16#ff, B band 16#ff, C band 16#ff, D band 16#ff]. diff --git a/src/statsderl_utils.erl b/src/statsderl_utils.erl index b325173..855b975 100644 --- a/src/statsderl_utils.erl +++ b/src/statsderl_utils.erl @@ -1,19 +1,22 @@ -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 --spec base_key(base_key()) -> iodata(). +-spec base_key(base_key()) -> + iodata(). base_key(hostname) -> [hostname(), $.]; @@ -35,7 +38,8 @@ base_key([H | T] = Key) -> [base_key(H) | base_key(T)] end. --spec error_msg(string(), [term()]) -> ok. +-spec error_msg(string(), [term()]) -> + ok. error_msg(Format, Data) -> error_logger:error_msg("[statsderl] " ++ Format, Data). @@ -56,36 +60,33 @@ getaddrs(Hostname) -> {error, Reason} end. --spec random(pos_integer()) -> pos_integer(). - -random(N) -> - granderl:uniform(N). - --spec random_element([term()]) -> term(). +-spec random_element([term()]) -> + term(). random_element([Element]) -> Element; random_element([_|_] = List) -> T = list_to_tuple(List), - Index = random(tuple_size(T)), + Index = granderl:uniform(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_profile.erl b/test/statsderl_profile.erl index 2896ec8..7417b94 100644 --- a/test/statsderl_profile.erl +++ b/test/statsderl_profile.erl @@ -1,25 +1,32 @@ -module(statsderl_profile). +-compile({parse_transform, statsderl_transform}). + -export([ fprofx/0 ]). --define(N, 1000). --define(P, 100). +-define(N, 10000). +-define(P, 20). %% public -spec fprofx() -> ok. fprofx() -> - preload_modules(), + statsderl_app:start(), + [counter() || _ <- lists:seq(1, 100)], + statsderl_app:stop(), + fprofx:start(), {ok, Tracer} = fprofx:profile(start), fprofx:trace([start, {procs, new}, {tracer, Tracer}]), - application:start(statsderl), + + {ok, [statsderl]} = statsderl_app:start(), + timer:sleep(250), Self = self(), [spawn(fun () -> - timing_now(), + counter(), Self ! exit end) || _ <- lists:seq(1, ?P)], wait(), @@ -32,16 +39,10 @@ fprofx() -> ok. %% private -timing_now() -> - Timestamp = os:timestamp(), - [statsderl:timing_now(["test", <<".test">>], Timestamp, 0.25) || +counter() -> + [statsderl:counter(["test", <<".test">>], 5, 0.25) || _ <- lists:seq(1, ?N)]. -preload_modules() -> - Filenames = filelib:wildcard("_build/default/lib/*/ebin/*.beam"), - Rootnames = [filename:rootname(Filename, ".beam") || Filename <- Filenames], - lists:foreach(fun code:load_abs/1, Rootnames). - wait() -> wait(?P). diff --git a/test/statsderl_tests.erl b/test/statsderl_tests.erl index c36d5b7..bfb454f 100644 --- a/test/statsderl_tests.erl +++ b/test/statsderl_tests.erl @@ -2,8 +2,7 @@ -include_lib("statsderl/include/statsderl.hrl"). -include_lib("eunit/include/eunit.hrl"). --spec test() -> ok. - +%% tests statsderl_base_key_test() -> assert_base_key("base_key", <<"base_key.">>), assert_base_key(<<"base_key">>, <<"base_key.">>). @@ -33,7 +32,9 @@ statsderl_test_() -> fun increment_subtest/1, fun sampling_rate_subtest/1, fun timing_fun_subtest/1, - fun timing_subtest/1 + fun timing_subtest/1, + fun timing_now_subtest/1, + fun timing_now_us_subtest/1 ]} }. @@ -63,15 +64,15 @@ increment_subtest(Socket) -> assert_packet(Socket, <<"test:1|c">>). sampling_rate_subtest(Socket) -> - meck:new(statsderl_utils, [passthrough, no_history]), - meck:expect(statsderl_utils, random, fun (?MAX_UNSIGNED_INT_32) -> 1 end), + meck:new(granderl, [passthrough, no_history]), + meck:expect(granderl, uniform, fun (?MAX_UNSIGNED_INT_32) -> 1 end), statsderl:counter("test", 1, 0.1234), assert_packet(Socket, <<"test:1|c|@0.123">>), - meck:expect(statsderl_utils, random, fun (?MAX_UNSIGNED_INT_32) -> + meck:expect(granderl, uniform, fun (?MAX_UNSIGNED_INT_32) -> ?MAX_UNSIGNED_INT_32 end), statsderl:counter("test", 1, 0.1234), - meck:unload(statsderl_utils). + meck:unload(granderl). timing_fun_subtest(Socket) -> meck:new(statsderl_utils, [passthrough, no_history]), @@ -85,6 +86,22 @@ timing_subtest(Socket) -> statsderl:timing("test", 1, 1), assert_packet(Socket, <<"test:1|ms">>). +timing_now_subtest(Socket) -> + meck:new(statsderl_utils, [passthrough, no_history]), + Seq = meck:loop([{1448, 573976, 400000}, {1448, 573976, 500000}]), + meck:expect(statsderl_utils, timestamp, [], Seq), + statsderl:timing_now("test", statsderl_utils:timestamp(), 1), + assert_packet(Socket, <<"test:100|ms">>), + meck:unload(statsderl_utils). + +timing_now_us_subtest(Socket) -> + meck:new(statsderl_utils, [passthrough, no_history]), + Seq = meck:loop([{1448, 573977, 400000}, {1448, 573977, 500000}]), + meck:expect(statsderl_utils, timestamp, [], Seq), + statsderl:timing_now_us("test", statsderl_utils:timestamp(), 1), + assert_packet(Socket, <<"test:100000|ms">>), + meck:unload(statsderl_utils). + %% helpers assert_base_key(BaseKey, Expected) -> Socket = setup([{?ENV_BASEKEY, BaseKey}]), diff --git a/test/statsderl_transform_tests.erl b/test/statsderl_transform_tests.erl new file mode 100644 index 0000000..95bf095 --- /dev/null +++ b/test/statsderl_transform_tests.erl @@ -0,0 +1,78 @@ +-module(statsderl_transform_tests). +-include_lib("statsderl/include/statsderl.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile({parse_transform, statsderl_transform}). + +%% tests +statsderl_test_() -> + {setup, + fun () -> setup() end, + fun (Socket) -> cleanup(Socket) end, + {with, [ + fun counter_subtest/1, + fun decrement_subtest/1, + fun gauge_subtest/1, + fun increment_subtest/1, + fun timing_fun_subtest/1 + ]} + }. + +%% subtests +counter_subtest(Socket) -> + A = "test", + B = 5, + C = 1, + statsderl:counter("test", 1, 1), + assert_packet(Socket, <<"test:1|c">>), + statsderl:counter(["test", A], 1, 1), + assert_packet(Socket, <<"testtest:1|c">>), + statsderl:counter("test", B, 1), + assert_packet(Socket, <<"test:5|c">>), + statsderl:counter("test", 1, C), + assert_packet(Socket, <<"test:1|c">>). + +decrement_subtest(Socket) -> + statsderl:decrement("test", 1, 1.0), + assert_packet(Socket, <<"test:-1|c">>). + +gauge_subtest(Socket) -> + A = "test", + B = 5, + C = 1, + statsderl:gauge("test", 1, 1), + assert_packet(Socket, <<"test:1|g">>), + statsderl:gauge(["test", A], 1, 1), + assert_packet(Socket, <<"testtest:1|g">>), + statsderl:gauge("test", B, 1), + assert_packet(Socket, <<"test:5|g">>), + statsderl:gauge("test", 1, C), + assert_packet(Socket, <<"test:1|g">>). + +increment_subtest(Socket) -> + statsderl:increment("test", 1, 1), + assert_packet(Socket, <<"test:1|c">>). + +timing_fun_subtest(Socket) -> + meck:new(statsderl_utils, [passthrough, no_history]), + Seq = meck:loop([{1448, 573975, 400000}, {1448, 573975, 500000}]), + meck:expect(statsderl_utils, timestamp, [], Seq), + statsderl:timing_fun("test", fun () -> ok end, 1), + assert_packet(Socket, <<"test:100|ms">>), + meck:unload(statsderl_utils). + +%% helpers +assert_packet(Socket, Expected) -> + {ok, {_Address, _Port, Packet}} = gen_udp:recv(Socket, 0), + ?assertEqual(Expected, Packet). + +cleanup(Socket) -> + ok = gen_udp:close(Socket), + statsderl_app:stop(). + +setup() -> + error_logger:tty(false), + application:load(?APP), + statsderl_app:start(), + {ok, Socket} = gen_udp:open(?DEFAULT_PORT, [binary, {active, false}]), + Socket.