diff --git a/README.md b/README.md index ebff90a..4076a3d 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ High Performance Erlang StatsD Client hostname - inet:ip_address() | inet:hostname() | binary() + inet:ip_address() | inet:hostname() | binary() | [{inet:ip_address() | inet:hostname() | binary(), inet:port_number()}] {127,0,0,1} server hostname @@ -41,7 +41,7 @@ High Performance Erlang StatsD Client port inet:port_number() 8125 - server port + server port. Not needed when using the list syntax in the hostname variable diff --git a/src/statsderl.erl b/src/statsderl.erl index 6a6037d..6db35b2 100644 --- a/src/statsderl.erl +++ b/src/statsderl.erl @@ -71,8 +71,9 @@ cast(OpCode, Key, Value, SampleRate) -> cast(OpCode, Key, Value, SampleRate, ServerName). cast(OpCode, Key, Value, SampleRate, ServerName) -> + KeyHash = erlang:phash2(iolist_to_binary(Key)), Packet = statsderl_protocol:encode(OpCode, Key, Value, SampleRate), - send(ServerName, {cast, Packet}). + send(ServerName, {cast, KeyHash, Packet}). maybe_cast(OpCode, Key, Value, 1) -> cast(OpCode, Key, Value, 1); diff --git a/src/statsderl_server.erl b/src/statsderl_server.erl index 63f5fca..ef07569 100644 --- a/src/statsderl_server.erl +++ b/src/statsderl_server.erl @@ -7,7 +7,7 @@ ]). -record(state, { - header :: iodata(), + headers :: tuple(), socket :: inet:socket() }). @@ -17,22 +17,26 @@ init(Parent, Name) -> BaseKey = ?ENV(?ENV_BASEKEY, ?DEFAULT_BASEKEY), Hostname = ?ENV(?ENV_HOSTNAME, ?DEFAULT_HOSTNAME), - Port = ?ENV(?ENV_PORT, ?DEFAULT_PORT), + {ok, Headers} = case is_list(Hostname) + andalso Hostname /= [] + andalso is_tuple(hd(Hostname)) of + true -> + generate_udp_headers(Hostname, BaseKey, []); + false -> + Port = ?ENV(?ENV_PORT, ?DEFAULT_PORT), + generate_udp_headers([{Hostname, Port}], + BaseKey, []) + end, - case udp_header(Hostname, Port, BaseKey) of - {ok, Header} -> - case gen_udp:open(0, [{active, false}]) of - {ok, Socket} -> - register(Name, self()), - proc_lib:init_ack(Parent, {ok, self()}), + case gen_udp:open(0, [{active, false}]) of + {ok, Socket} -> + register(Name, self()), + proc_lib:init_ack(Parent, {ok, self()}), - loop(#state { - socket = Socket, - header = Header - }); - {error, Reason} -> - exit(Reason) - end; + loop(#state { + socket = Socket, + headers = list_to_tuple(Headers) + }); {error, Reason} -> exit(Reason) end. @@ -43,11 +47,12 @@ start_link(Name) -> proc_lib:start_link(?MODULE, init, [self(), Name]). %% private -handle_msg({cast, Packet}, #state { - header = Header, +handle_msg({cast, KeyHash, Packet}, #state { + headers = Headers, socket = Socket } = State) -> + Header = element((KeyHash rem tuple_size(Headers)) + 1, Headers), statsderl_udp:send(Socket, Header, Packet), {ok, State}; handle_msg({inet_reply, _Socket, ok}, State) -> @@ -62,6 +67,16 @@ loop(State) -> loop(State2) end. +generate_udp_headers([], _BaseKey, Acc) -> + {ok, lists:reverse(Acc)}; +generate_udp_headers([{Hostname, Port} | Rest], BaseKey, Acc) -> + case udp_header(Hostname, Port, BaseKey) of + {ok, Header} -> + generate_udp_headers(Rest, BaseKey, [Header | Acc]); + Error -> + Error + end. + udp_header(Hostname, Port, BaseKey) -> case statsderl_utils:getaddrs(Hostname) of {ok, {A, B, C, D}} -> diff --git a/test/statsderl_tests.erl b/test/statsderl_tests.erl index 0dc6d85..b5bd2a1 100644 --- a/test/statsderl_tests.erl +++ b/test/statsderl_tests.erl @@ -20,6 +20,19 @@ statsderl_hostname_test() -> meck:unload(statsderl_utils), cleanup(Socket). +statsderl_multiple_hostname_test() -> + meck:new(statsderl_utils, [passthrough, no_history]), + meck:expect(statsderl_utils, getaddrs, fun (_) -> + {ok, {127, 0, 0, 1}} + end), + Socket = setup([{?ENV_HOSTNAME, + [{<<"adgear.com">>, ?DEFAULT_PORT}, {"whatever.com", ?DEFAULT_PORT}]}]), + statsderl:counter("test", 1, 1), + {ok, {_Address, _Port, Packet}} = gen_udp:recv(Socket, 0), + ?assertEqual(<<"test:1|c">>, Packet), + meck:unload(statsderl_utils), + cleanup(Socket). + statsderl_test_() -> {setup, fun () -> setup() end,