diff --git a/bin/rebar3 b/bin/rebar3 index a5f263e..e550663 100755 Binary files a/bin/rebar3 and b/bin/rebar3 differ diff --git a/include/statsderl.hrl b/include/statsderl.hrl index 42709d6..92296a4 100644 --- a/include/statsderl.hrl +++ b/include/statsderl.hrl @@ -1,17 +1,20 @@ %% macros -define(APP, statsderl). -define(CHILD(Name, Mod), {Name, {Mod, start_link, [Name]}, permanent, 5000, worker, [Mod]}). +-define(CLIENT, statsderl_client). +-define(MAX_UNSIGNED_INT_32, 4294967295). + +%% env vars -define(ENV(Key, Default), application:get_env(?APP, Key, Default)). -define(ENV_BASEKEY, base_key). -define(ENV_HOSTNAME, hostname). -define(ENV_PORT, port). -define(ENV_VARS, [?ENV_BASEKEY, ?ENV_HOSTNAME, ?ENV_PORT]). --define(MAX_UNSIGNED_INT_32, 4294967295). --define(SERVER, statsderl_server). %% defaults +-define(DEFAULT_BACKLOG_SIZE, 4096). -define(DEFAULT_BASEKEY, undefined). --define(DEFAULT_HOSTNAME, {127, 0, 0, 1}). +-define(DEFAULT_HOSTNAME, "127.0.0.1"). -define(DEFAULT_POOL_SIZE, 4). -define(DEFAULT_PORT, 8125). diff --git a/rebar.config b/rebar.config index 9aaef02..1a56fac 100644 --- a/rebar.config +++ b/rebar.config @@ -1,10 +1,9 @@ {cover_export_enabled, true}. {deps, [ - {foil, "0.1.0"}, {granderl, "0.1.5"}, - {metal, "0.1.1"}, - {parse_trans, "3.0.0"} + {parse_trans, "3.0.0"}, + {shackle, "0.6.11"} ]}. {edoc_opts, [ @@ -21,6 +20,8 @@ debug_info ]}. +{plugins, [rebar3_hex]}. + {profiles, [ {compile, [ {erl_opts, [ diff --git a/rebar.config.script b/rebar.config.script index fdbb49c..da73697 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -3,13 +3,11 @@ case erlang:function_exported(rebar3, main, 1) of CONFIG; false -> [{deps, [ - {foil, ".*", - {git, "https://github.com/lpgauth/foil.git", {tag, "0.1.0"}}}, {granderl, ".*", {git, "https://github.com/tokenrove/granderl.git", {tag, "v0.1.5"}}}, - {metal, ".*", - {git, "https://github.com/lpgauth/metal.git", {tag, "0.1.1"}}}, {parse_trans, ".*", - {git, "https://github.com/uwiger/parse_trans.git", {tag, "3.0.0"}}} + {git, "https://github.com/uwiger/parse_trans.git", {tag, "3.0.0"}}}, + {shackle, ".*", + {git, "https://github.com/lpgauth/shackle.git", {tag, "0.6.11"}}} ]} | lists:keydelete(deps, 1, CONFIG)] end. diff --git a/rebar.lock b/rebar.lock index 06b6fa8..af7d361 100644 --- a/rebar.lock +++ b/rebar.lock @@ -2,11 +2,13 @@ [{<<"foil">>,{pkg,<<"foil">>,<<"0.1.0">>},0}, {<<"granderl">>,{pkg,<<"granderl">>,<<"0.1.5">>},0}, {<<"metal">>,{pkg,<<"metal">>,<<"0.1.1">>},0}, - {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.0.0">>},0}]}. + {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.0.0">>},0}, + {<<"shackle">>,{pkg,<<"shackle">>,<<"0.6.11">>},0}]}. [ {pkg_hash,[ {<<"foil">>, <<"16406493144743F633505E51406F7BE6BF17A3772DFD62BCDE3107C654704BEE">>}, {<<"granderl">>, <<"F20077A68BD80B8D8783BD15A052813C6483771DEC1A5B837D307CBE92F14122">>}, {<<"metal">>, <<"5D3D1322DA7BCD34B94FED5486F577973685298883954F7A3E517EF5EF6953F5">>}, - {<<"parse_trans">>, <<"9E96B1C9C3A0DF54E7B76F8F685D38BFA1EB21B31E042B1D1A5A70258E4DB1E3">>}]} + {<<"parse_trans">>, <<"9E96B1C9C3A0DF54E7B76F8F685D38BFA1EB21B31E042B1D1A5A70258E4DB1E3">>}, + {<<"shackle">>, <<"949245E1AE690DECAD887B046CCCA595E5F3FC798A317131466985F5BBA64298">>}]} ]. diff --git a/src/statsderl.app.src b/src/statsderl.app.src index ef564e1..503bccd 100644 --- a/src/statsderl.app.src +++ b/src/statsderl.app.src @@ -1,5 +1,5 @@ {application, statsderl, [ - {applications, [kernel, stdlib, granderl, foil, metal]}, + {applications, [kernel, stdlib, granderl, shackle]}, {description, "High Performance Erlang StatsD Client"}, {env, []}, {licenses, ["MIT"]}, diff --git a/src/statsderl.erl b/src/statsderl.erl index 07ce6ba..1284c6f 100644 --- a/src/statsderl.erl +++ b/src/statsderl.erl @@ -23,43 +23,43 @@ ok. counter(Key, Value, Rate) -> - statsderl_pool:sample(Rate, {counter, Key, Value, Rate}). + statsderl_sample:rate(Rate, {counter, Key, Value, Rate}). -spec decrement(key(), value(), sample_rate()) -> ok. decrement(Key, Value, Rate) when Value >= 0 -> - statsderl_pool:sample(Rate, {counter, Key, -Value, Rate}). + statsderl_sample:rate(Rate, {counter, Key, -Value, Rate}). -spec gauge(key(), value(), sample_rate()) -> ok. gauge(Key, Value, Rate) when Value >= 0 -> - statsderl_pool:sample(Rate, {gauge, Key, Value}). + statsderl_sample:rate(Rate, {gauge, Key, Value}). -spec gauge_decrement(key(), value(), sample_rate()) -> ok. gauge_decrement(Key, Value, Rate) when Value >= 0 -> - statsderl_pool:sample(Rate, {gauge_decrement, Key, Value}). + statsderl_sample:rate(Rate, {gauge_decrement, Key, Value}). -spec gauge_increment(key(), value(), sample_rate()) -> ok. gauge_increment(Key, Value, Rate) when Value >= 0 -> - statsderl_pool:sample(Rate, {gauge_increment, Key, Value}). + statsderl_sample:rate(Rate, {gauge_increment, Key, Value}). -spec increment(key(), value(), sample_rate()) -> ok. increment(Key, Value, Rate) when Value >= 0 -> - statsderl_pool:sample(Rate, {counter, Key, Value, Rate}). + statsderl_sample:rate(Rate, {counter, Key, Value, Rate}). -spec timing(key(), value(), sample_rate()) -> ok. timing(Key, Value, Rate) -> - statsderl_pool:sample(Rate, {timing, Key, Value}). + statsderl_sample:rate(Rate, {timing, Key, Value}). -spec timing_fun(key(), fun(), sample_rate()) -> any(). @@ -74,10 +74,10 @@ timing_fun(Key, Fun, Rate) -> ok. timing_now(Key, Timestamp, Rate) -> - statsderl_pool:sample(Rate, {timing_now, Key, Timestamp}). + statsderl_sample:rate(Rate, {timing_now, Key, Timestamp}). -spec timing_now_us(key(), erlang:timestamp(), sample_rate()) -> ok. timing_now_us(Key, Timestamp, Rate) -> - statsderl_pool:sample(Rate, {timing_now_us, Key, Timestamp}). + statsderl_sample:rate(Rate, {timing_now_us, Key, Timestamp}). diff --git a/src/statsderl_app.erl b/src/statsderl_app.erl index 43113b5..d5b0501 100644 --- a/src/statsderl_app.erl +++ b/src/statsderl_app.erl @@ -36,4 +36,5 @@ start(_StartType, _StartArgs) -> ok. stop(_State) -> + shackle_pool:stop(?APP), ok. diff --git a/src/statsderl_client.erl b/src/statsderl_client.erl new file mode 100644 index 0000000..c3fd6ee --- /dev/null +++ b/src/statsderl_client.erl @@ -0,0 +1,64 @@ +-module(statsderl_client). +-include("statsderl.hrl"). + +-compile(inline). +-compile({inline_size, 512}). + +-behavior(shackle_client). +-export([ + init/1, + setup/2, + handle_request/2, + handle_data/2, + terminate/1 +]). + +-record(state, { + base_key :: iodata() +}). + +-type state() :: #state {}. + +%% shackle_server callbacks +-spec init(undefined) -> + {ok, state()}. + +init(_Opts) -> + BaseKey = ?ENV(?ENV_BASEKEY, ?DEFAULT_BASEKEY), + + {ok, #state { + base_key = statsderl_utils:base_key(BaseKey) + }}. + +-spec setup(inet:socket(), state()) -> + {ok, state()}. + +setup(_Socket, State) -> + {ok, State}. + +-spec handle_request(term(), state()) -> + {ok, undefined, iolist(), state()}. + +handle_request({cast, Data}, #state { + base_key = BaseKey + } = State) -> + + {ok, undefined, [BaseKey, Data], State}; +handle_request(Request, #state { + base_key = BaseKey + } = State) -> + + Data = statsderl_protocol:encode(Request), + + {ok, undefined, [BaseKey, Data], State}. + +-spec handle_data(binary(), state()) -> + {ok, [], state()}. + +handle_data(_Data, State) -> + {ok, [], State}. + +-spec terminate(state()) -> ok. + +terminate(_State) -> + ok. diff --git a/src/statsderl_pool.erl b/src/statsderl_pool.erl deleted file mode 100644 index 81110ad..0000000 --- a/src/statsderl_pool.erl +++ /dev/null @@ -1,114 +0,0 @@ --module(statsderl_pool). --include("statsderl.hrl"). - --compile(inline). --compile({inline_size, 512}). - --ignore_xref([ - {statsderl_pool_foil, lookup, 1} -]). - --export([ - init/0, - sample/2, - sample_scaled/2, - server_name/1, - size/0 -]). - -%% public --spec init() -> - ok. - -init() -> - foil:new(statsderl_pool), - PoolSize = ?ENV(pool_size, ?DEFAULT_POOL_SIZE), - foil:insert(statsderl_pool, pool_size, PoolSize), - [foil:insert(statsderl_pool, N, server_name_gen(N)) || - N <- lists:seq(1, PoolSize)], - foil:load(statsderl_pool). - --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 size() + 1, - operation(Operation, server_name(N)); - false -> - ok - end. - --spec server_name(pos_integer()) -> - atom(). - -server_name(N) -> - try statsderl_pool_foil:lookup(N) of - {ok, Value} -> - Value; - {error, _Reason} -> - undefined - catch - error:undef -> - undefined - end. - --spec size() -> - pool_size(). - -size() -> - try foil:lookup(?MODULE, pool_size) of - {ok, Value} -> - Value; - {error, _Reason} -> - 1 - catch - error:undef -> - 1 - end. - -%% 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(size())). - -send(ServerName, Msg) -> - try - ServerName ! Msg, - ok - catch - _:_ -> - ok - end. - -server_name_gen(N) -> - list_to_atom("statsderl_" ++ integer_to_list(N)). diff --git a/src/statsderl_sample.erl b/src/statsderl_sample.erl new file mode 100644 index 0000000..f204e1c --- /dev/null +++ b/src/statsderl_sample.erl @@ -0,0 +1,48 @@ +-module(statsderl_sample). +-include("statsderl.hrl"). + +-compile(inline). +-compile({inline_size, 512}). + +-export([ + rate/2, + rate_scaled/2 +]). + +%% public +-spec rate(sample_rate(), operation()) -> + ok. + +rate(1, Operation) -> + operation(Operation); +rate(1.0, Operation) -> + operation(Operation); +rate(Rate, Operation) -> + RateInt = trunc(Rate * ?MAX_UNSIGNED_INT_32), + rate_scaled(RateInt, Operation). + +-spec rate_scaled(non_neg_integer(), operation()) -> + ok. + +rate_scaled(RateInt, Operation) -> + Rand = granderl:uniform(?MAX_UNSIGNED_INT_32), + case Rand =< RateInt of + true -> + operation(Operation); + false -> + ok + end. + +%% private +cast(Request) -> + shackle:cast(?APP, Request, undefined), + ok. + +operation({timing_now, Key, Value}) -> + Value2 = statsderl_utils:timing_now(Value), + cast({timing, Key, Value2}); +operation({timing_now_us, Key, Value}) -> + Value2 = statsderl_utils:timing_now_us(Value), + cast({timing, Key, Value2}); +operation(Operation) -> + cast(Operation). diff --git a/src/statsderl_server.erl b/src/statsderl_server.erl deleted file mode 100644 index d5edd3d..0000000 --- a/src/statsderl_server.erl +++ /dev/null @@ -1,69 +0,0 @@ --module(statsderl_server). --include("statsderl.hrl"). - --compile(inline). --compile({inline_size, 512}). - --export([ - start_link/1 -]). - --behaviour(metal). --export([ - init/3, - handle_msg/2, - terminate/2 -]). - --record(state, { - base_key :: iodata(), - socket :: inet:socket() -}). - -%% public --spec start_link(atom()) -> - {ok, pid()}. - -start_link(Name) -> - metal:start_link(?SERVER, Name, undefined). - -%% metal callbacks --spec init(atom(), pid(), term()) -> - {ok, term()} | {stop, atom()}. - -init(_Name, _Parent, _Opts) -> - case gen_udp:open(0, [{active, false}]) of - {ok, Socket} -> - BaseKey = ?ENV(?ENV_BASEKEY, ?DEFAULT_BASEKEY), - Hostname = ?ENV(?ENV_HOSTNAME, ?DEFAULT_HOSTNAME), - Port = ?ENV(?ENV_PORT, ?DEFAULT_PORT), - gen_udp:connect(Socket, Hostname, Port), - - {ok, #state { - base_key = statsderl_utils:base_key(BaseKey), - socket = Socket - }}; - {error, Reason} -> - {stop, Reason} - end. - --spec handle_msg(term(), term()) -> - {ok, term()}. - -handle_msg({cast, Packet}, #state { - base_key = BaseKey, - socket = Socket - } = State) -> - - gen_udp:send(Socket, [BaseKey, Packet]), - {ok, State}. - --spec terminate(term(), term()) -> - ok. - -terminate(_Reason, #state { - socket = Socket - }) -> - - gen_udp:close(Socket), - ok. diff --git a/src/statsderl_sup.erl b/src/statsderl_sup.erl index 7a8e43b..1a16095 100644 --- a/src/statsderl_sup.erl +++ b/src/statsderl_sup.erl @@ -21,16 +21,23 @@ start_link() -> %% supervisor callbacks -spec init([]) -> - {ok, {{one_for_one, 5, 10}, [supervisor:child_spec()]}}. + {ok, {{one_for_one, 5, 10}, []}}. init(_Args) -> - ok = statsderl_pool:init(), - PoolSize = statsderl_pool:size(), - {ok, {{one_for_one, 5, 10}, child_specs(PoolSize)}}. - -%% private -child_specs(0) -> - []; -child_specs(N) -> - Name = statsderl_pool:server_name(N), - [?CHILD(Name, ?SERVER) | child_specs(N - 1)]. + BacklogSize = ?ENV(backlog_size, ?DEFAULT_BACKLOG_SIZE), + Hostname = ?ENV(?ENV_HOSTNAME, ?DEFAULT_HOSTNAME), + PoolSize = ?ENV(pool_size, ?DEFAULT_POOL_SIZE), + Port = ?ENV(?ENV_PORT, ?DEFAULT_PORT), + + ClientOpts = [ + {address, Hostname}, + {port, Port}, + {protocol, shackle_udp} + ], + PoolOtps = [ + {backlog_size, BacklogSize}, + {pool_size, PoolSize} + ], + ok = shackle_pool:start(?APP, ?CLIENT, ClientOpts, PoolOtps), + + {ok, {{one_for_one, 5, 10}, []}}. diff --git a/src/statsderl_transform.erl b/src/statsderl_transform.erl index 2914b07..f236b1c 100644 --- a/src/statsderl_transform.erl +++ b/src/statsderl_transform.erl @@ -30,7 +30,7 @@ do_transform(_Form) -> %% private call(Function, Arg1, Arg2) -> {call, 0, {remote, 0, - {atom, 0, statsderl_pool}, + {atom, 0, statsderl_sample}, {atom, 0, Function}}, [Arg1, Arg2] }. @@ -110,10 +110,10 @@ replace(Function, {_, _, _, [Key, Value, Rate]} = F) -> end. sample(Rate, Arguments) -> - call(sample, Rate, Arguments). + call(rate, Rate, Arguments). sample_scaled(RateScaled, Arguments) -> - call(sample_scaled, ?INTEGER(RateScaled), Arguments). + call(rate_scaled, ?INTEGER(RateScaled), Arguments). safe_normalize(AbsTerm) -> try erl_parse:normalise(AbsTerm) diff --git a/test/statsderl_tests.erl b/test/statsderl_tests.erl index e909689..8ca89c0 100644 --- a/test/statsderl_tests.erl +++ b/test/statsderl_tests.erl @@ -53,11 +53,15 @@ increment_subtest(Socket) -> sampling_rate_subtest(Socket) -> meck:new(granderl, [passthrough, no_history]), - meck:expect(granderl, uniform, fun (?MAX_UNSIGNED_INT_32) -> 1 end), + meck:expect(granderl, uniform, fun + (4) -> 2; + (?MAX_UNSIGNED_INT_32) -> 1 + end), statsderl:counter("test", 1, 0.1234), assert_packet(Socket, <<"test:1|c|@0.1234">>), - meck:expect(granderl, uniform, fun (?MAX_UNSIGNED_INT_32) -> - ?MAX_UNSIGNED_INT_32 + meck:expect(granderl, uniform, fun + (4) -> 2; + (?MAX_UNSIGNED_INT_32) -> ?MAX_UNSIGNED_INT_32 end), statsderl:counter("test", 1, 0.1234), meck:unload(granderl). @@ -116,6 +120,8 @@ setup(EnvVars) -> application:load(?APP), [application:unset_env(?APP, K) || K <- ?ENV_VARS], [application:set_env(?APP, K, V) || {K, V} <- EnvVars], - statsderl_app:start(), {ok, Socket} = gen_udp:open(?DEFAULT_PORT, [binary, {active, false}]), + timer:sleep(100), + statsderl_app:start(), + timer:sleep(1000), Socket. diff --git a/test/statsderl_transform_tests.erl b/test/statsderl_transform_tests.erl index 95bf095..e1eabb2 100644 --- a/test/statsderl_transform_tests.erl +++ b/test/statsderl_transform_tests.erl @@ -73,6 +73,8 @@ cleanup(Socket) -> setup() -> error_logger:tty(false), application:load(?APP), - statsderl_app:start(), {ok, Socket} = gen_udp:open(?DEFAULT_PORT, [binary, {active, false}]), + timer:sleep(100), + statsderl_app:start(), + timer:sleep(1000), Socket.