/workers/amqp/src/amqp_worker.erl

https://github.com/machinezone/mzbench · Erlang · 169 lines · 145 code · 23 blank · 1 comment · 2 complexity · e9fa607f9a160ae9387a739ad08a070d MD5 · raw file

  1. -module(amqp_worker).
  2. -export([initial_state/0, metrics/0]).
  3. -export([connect/3, disconnect/2,
  4. declare_exchange/3, declare_queue/3, bind/5,
  5. publish/4, publish/5, get/3, subscribe/3]).
  6. -export([consumer/2, consumer_loop/2]).
  7. -include_lib("amqp_client/include/amqp_client.hrl").
  8. -define(DEFAULT_X, <<>>).
  9. -record(s, {
  10. prefix = "default",
  11. connection = undefined,
  12. channel = undefined,
  13. consumer_pid = undefined,
  14. subscription_tag = undefined
  15. }).
  16. -record(payload, {
  17. data = <<>> :: binary(),
  18. timestamp = erlang:now() :: erlang:timestamp()
  19. }).
  20. -define(TIMED(Name, Expr),
  21. (fun () ->
  22. StartTime = os:timestamp(),
  23. Result = Expr,
  24. Value = timer:now_diff(os:timestamp(), StartTime),
  25. mzb_metrics:notify({Name, histogram}, Value),
  26. Result
  27. end)()).
  28. initial_state() -> #s{}.
  29. metrics() -> metrics("default").
  30. metrics(Prefix) ->
  31. [
  32. {group, "AMQP (" ++ Prefix ++ ")", [
  33. {graph, #{title => "Messages",
  34. units => "N",
  35. metrics => [{Prefix ++ ".publish", counter}, {Prefix ++ ".get", counter}, {Prefix ++ ".consume", counter}]}},
  36. {graph, #{title => "Latency",
  37. units => "microseconds",
  38. metrics => [{Prefix ++ ".latency", histogram}]}}
  39. ]}
  40. ].
  41. connect(_State, _Meta, Param) ->
  42. UserName = proplists:get_value(username, Param, <<"guest">>),
  43. Password = proplists:get_value(password, Param, <<"guest">>),
  44. UserNameBin = if is_list(UserName) -> list_to_binary(UserName);
  45. true -> UserName end,
  46. PasswordBin = if is_list(Password) -> list_to_binary(Password);
  47. true -> Password end,
  48. Host = proplists:get_value(host, Param, "localhost"),
  49. Port = proplists:get_value(port, Param, 5672),
  50. {ok, Connection} = amqp_connection:start(#amqp_params_network{
  51. username = UserNameBin,
  52. password = PasswordBin,
  53. host = Host,
  54. port = Port
  55. }),
  56. {ok, Channel} = amqp_connection:open_channel(Connection),
  57. {nil, #s{connection = Connection, channel = Channel}}.
  58. disconnect(#s{connection = Connection, channel = Channel,
  59. consumer_pid = Consumer, subscription_tag = Tag}, _Meta) ->
  60. case Consumer of
  61. undefined -> ok;
  62. _ -> amqp_channel:call(Channel, #'basic.cancel'{consumer_tag = Tag})
  63. end,
  64. amqp_channel:close(Channel),
  65. amqp_connection:close(Connection),
  66. {nil, initial_state()}.
  67. declare_queue(State, Meta, InQ) when is_list(InQ) ->
  68. declare_queue(State, Meta, list_to_binary(InQ));
  69. declare_queue(#s{channel = Channel} = State, _Meta, InQ) ->
  70. Declare = #'queue.declare'{queue = InQ, auto_delete = true, arguments = [{<<"x-expires">>, long, 60000}]},
  71. #'queue.declare_ok'{} = amqp_channel:call(Channel, Declare),
  72. {nil, State}.
  73. declare_exchange(State, Meta, X) when is_list(X) ->
  74. declare_exchange(State, Meta, list_to_binary(X));
  75. declare_exchange(#s{channel = Channel} = State, _Meta, X) ->
  76. Declare = #'exchange.declare'{exchange = X, auto_delete = true},
  77. #'exchange.declare_ok'{} = amqp_channel:call(Channel, Declare),
  78. {nil, State}.
  79. bind(State, Meta, X, RoutingKey, InQ) when is_list(InQ) ->
  80. bind(State, Meta, X, RoutingKey, list_to_binary(InQ));
  81. bind(State, Meta, X, RoutingKey, InQ) when is_list(X) ->
  82. bind(State, Meta, list_to_binary(X), RoutingKey, InQ);
  83. bind(State, Meta, X, RoutingKey, InQ) when is_list(RoutingKey) ->
  84. bind(State, Meta, X, list_to_binary(RoutingKey), InQ);
  85. bind(#s{channel = Channel} = State, _Meta, X, RoutingKey, InQ) ->
  86. amqp_channel:call(Channel, #'queue.bind'{queue = InQ, exchange = X, routing_key = RoutingKey}),
  87. {nil, State}.
  88. publish(State, Meta, RoutingKey, Payload) ->
  89. publish(State, Meta, ?DEFAULT_X, RoutingKey, Payload).
  90. publish(State, Meta, X, RoutingKey, Payload) when is_list(X) ->
  91. publish(State, Meta, list_to_binary(X), RoutingKey, Payload);
  92. publish(State, Meta, X, RoutingKey, Payload) when is_list(RoutingKey) ->
  93. publish(State, Meta, X, list_to_binary(RoutingKey), Payload);
  94. publish(State, Meta, X, RoutingKey, Payload) when is_list(Payload) ->
  95. publish(State, Meta, X, RoutingKey, list_to_binary(Payload));
  96. publish(#s{channel = Channel, prefix = Prefix} = State, _Meta, X, RoutingKey, Payload) ->
  97. Publish = #'basic.publish'{exchange = X, routing_key = RoutingKey},
  98. Binary = erlang:term_to_binary(#payload{data = Payload}),
  99. ok = amqp_channel:call(Channel, Publish, #amqp_msg{payload = Binary}),
  100. mzb_metrics:notify({Prefix ++ ".publish", counter}, 1),
  101. {nil, State}.
  102. get(State, Meta, InQ) when is_list(InQ) ->
  103. get(State, Meta, list_to_binary(InQ));
  104. get(#s{channel = Channel, prefix = Prefix} = State, _Meta, InQ) ->
  105. Get = #'basic.get'{queue = InQ, no_ack = true},
  106. Response = amqp_channel:call(Channel, Get),
  107. case Response of
  108. {#'basic.get_ok'{}, Content} ->
  109. #amqp_msg{payload = Payload} = Content,
  110. #payload{timestamp = Now1} = erlang:binary_to_term(Payload),
  111. mzb_metrics:notify({Prefix ++ ".get", counter}, 1),
  112. mzb_metrics:notify({Prefix ++ ".latency", histogram}, timer:now_diff(erlang:now(), Now1));
  113. #'basic.get_empty'{} ->
  114. nop
  115. end,
  116. {nil, State}.
  117. subscribe(State, Meta, InQ) when is_list(InQ) ->
  118. subscribe(State, Meta, list_to_binary(InQ));
  119. subscribe(#s{channel = Channel, prefix = Prefix} = State, _Meta, InQ) ->
  120. Consumer = spawn_link(?MODULE, consumer, [Channel, Prefix]),
  121. Sub = #'basic.consume'{queue = InQ},
  122. #'basic.consume_ok'{consumer_tag = Tag} = amqp_channel:subscribe(Channel, Sub, Consumer),
  123. {Consumer, State#s{consumer_pid = Consumer, subscription_tag = Tag}}.
  124. consumer(Channel, Prefix) ->
  125. erlang:monitor(process, Channel),
  126. consumer_loop(Channel, Prefix).
  127. %% Internal functions
  128. consumer_loop(Channel, Prefix) ->
  129. receive
  130. #'basic.consume_ok'{} ->
  131. consumer_loop(Channel, Prefix);
  132. #'basic.cancel_ok'{} ->
  133. ok;
  134. {#'basic.deliver'{delivery_tag = Tag}, Content} ->
  135. #amqp_msg{payload = Payload} = Content,
  136. #payload{timestamp = Now1} = erlang:binary_to_term(Payload),
  137. mzb_metrics:notify({Prefix ++ ".consume", counter}, 1),
  138. mzb_metrics:notify({Prefix ++ ".latency", histogram}, timer:now_diff(erlang:now(), Now1)),
  139. amqp_channel:call(Channel, #'basic.ack'{delivery_tag = Tag}),
  140. consumer_loop(Channel, Prefix);
  141. {'DOWN', _, _, _, _} ->
  142. ok
  143. end.
  144. set_prefix(State, _Meta, NewPrefix) ->
  145. mzb_metrics:declare_metrics(metrics(NewPrefix)),
  146. {nil, State#s{prefix = NewPrefix}}.