--- /dev/null
+From: Alexey Lebedeff <alebedev@mirantis.com>
+Date: Wed, 9 Mar 2016 18:09:04 +0300
+Subject: [PATCH] Avoid RPC roundtrips in list commands
+
+Current implementation of various `list_XXX` commands require cross-node
+roundtrip for every processed item - because `rabbitmqctl` target node
+is responsible for gathering global list of all items of
+interest (channels etc.) and then processing them one by one.
+
+For example, listing 10000 channels evenly distributed across 3 nodes
+where network has 1ms delay takes more than 10 seconds on my
+machine. And with the proposed change listing will take almost the same
+time as it'll take to gather this info locally. E.g. in the case above
+listing now takes 0.7 second on the same machine with same 1ms delay.
+
+It works by invoking emitting_map on every node, where it should send
+info about only local items to aggregator, in an async fashion - as no
+reply from aggregator is needed.
+
+diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl
+index ea9d6a2..e6b168a 100644
+--- a/src/rabbit_control_main.erl
++++ b/src/rabbit_control_main.erl
+@@ -23,7 +23,7 @@
+ sync_queue/1, cancel_sync_queue/1, become/1,
+ purge_queue/1]).
+
+--import(rabbit_misc, [rpc_call/4, rpc_call/5, rpc_call/7]).
++-import(rabbit_misc, [rpc_call/4, rpc_call/5]).
+
+ -define(EXTERNAL_CHECK_INTERVAL, 1000).
+
+@@ -595,56 +595,74 @@ action(purge_queue, Node, [Q], Opts, Inform, Timeout) ->
+
+ action(list_users, Node, [], _Opts, Inform, Timeout) ->
+ Inform("Listing users", []),
+- call(Node, {rabbit_auth_backend_internal, list_users, []},
+- rabbit_auth_backend_internal:user_info_keys(), true, Timeout);
++ call_emitter(Node, {rabbit_auth_backend_internal, list_users, []},
++ rabbit_auth_backend_internal:user_info_keys(),
++ [{timeout, Timeout}, to_bin_utf8]);
+
+ action(list_permissions, Node, [], Opts, Inform, Timeout) ->
+ VHost = proplists:get_value(?VHOST_OPT, Opts),
+ Inform("Listing permissions in vhost \"~s\"", [VHost]),
+- call(Node, {rabbit_auth_backend_internal, list_vhost_permissions, [VHost]},
+- rabbit_auth_backend_internal:vhost_perms_info_keys(), true, Timeout,
+- true);
++ call_emitter(Node, {rabbit_auth_backend_internal, list_vhost_permissions, [VHost]},
++ rabbit_auth_backend_internal:vhost_perms_info_keys(),
++ [{timeout, Timeout}, to_bin_utf8, is_escaped]);
+
+ action(list_parameters, Node, [], Opts, Inform, Timeout) ->
+ VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
+ Inform("Listing runtime parameters", []),
+- call(Node, {rabbit_runtime_parameters, list_formatted, [VHostArg]},
+- rabbit_runtime_parameters:info_keys(), Timeout);
++ call_emitter(Node, {rabbit_runtime_parameters, list_formatted, [VHostArg]},
++ rabbit_runtime_parameters:info_keys(),
++ [{timeout, Timeout}]);
+
+ action(list_policies, Node, [], Opts, Inform, Timeout) ->
+ VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
+ Inform("Listing policies", []),
+- call(Node, {rabbit_policy, list_formatted, [VHostArg]},
+- rabbit_policy:info_keys(), Timeout);
++ call_emitter(Node, {rabbit_policy, list_formatted, [VHostArg]},
++ rabbit_policy:info_keys(),
++ [{timeout, Timeout}]);
+
+ action(list_vhosts, Node, Args, _Opts, Inform, Timeout) ->
+ Inform("Listing vhosts", []),
+ ArgAtoms = default_if_empty(Args, [name]),
+- call(Node, {rabbit_vhost, info_all, []}, ArgAtoms, true, Timeout);
++ call_emitter(Node, {rabbit_vhost, info_all, []}, ArgAtoms,
++ [{timeout, Timeout}, to_bin_utf8]);
+
+ action(list_user_permissions, _Node, _Args = [], _Opts, _Inform, _Timeout) ->
+ {error_string,
+ "list_user_permissions expects a username argument, but none provided."};
+ action(list_user_permissions, Node, Args = [_Username], _Opts, Inform, Timeout) ->
+ Inform("Listing permissions for user ~p", Args),
+- call(Node, {rabbit_auth_backend_internal, list_user_permissions, Args},
+- rabbit_auth_backend_internal:user_perms_info_keys(), true, Timeout,
+- true);
++ call_emitter(Node, {rabbit_auth_backend_internal, list_user_permissions, Args},
++ rabbit_auth_backend_internal:user_perms_info_keys(),
++ [{timeout, Timeout}, to_bin_utf8, is_escaped]);
+
+ action(list_queues, Node, Args, Opts, Inform, Timeout) ->
+- [Online, Offline] = rabbit_cli:filter_opts(Opts, [?ONLINE_OPT, ?OFFLINE_OPT]),
+ Inform("Listing queues", []),
++ %% User options
++ [Online, Offline] = rabbit_cli:filter_opts(Opts, [?ONLINE_OPT, ?OFFLINE_OPT]),
+ VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
+ ArgAtoms = default_if_empty(Args, [name, messages]),
+- call(Node, {rabbit_amqqueue, info_all, [VHostArg, ArgAtoms, Online, Offline]},
+- ArgAtoms, Timeout);
++
++ %% Data for emission
++ Nodes = nodes_in_cluster(Node, Timeout),
++ OnlineChunks = if Online -> length(Nodes); true -> 0 end,
++ OfflineChunks = if Offline -> 1; true -> 0 end,
++ ChunksOpt = {chunks, OnlineChunks + OfflineChunks},
++ TimeoutOpt = {timeout, Timeout},
++ EmissionRef = make_ref(),
++ EmissionRefOpt = {ref, EmissionRef},
++
++ _ = Online andalso start_emission(Node, {rabbit_amqqueue, emit_info_all, [Nodes, VHostArg, ArgAtoms]},
++ [TimeoutOpt, EmissionRefOpt]),
++ _ = Offline andalso start_emission(Node, {rabbit_amqqueue, emit_info_down, [VHostArg, ArgAtoms]},
++ [TimeoutOpt, EmissionRefOpt]),
++ display_emission_result(EmissionRef, ArgAtoms, [ChunksOpt, TimeoutOpt]);
+
+ action(list_exchanges, Node, Args, Opts, Inform, Timeout) ->
+ Inform("Listing exchanges", []),
+ VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
+ ArgAtoms = default_if_empty(Args, [name, type]),
+- call(Node, {rabbit_exchange, info_all, [VHostArg, ArgAtoms]},
+- ArgAtoms, Timeout);
++ call_emitter(Node, {rabbit_exchange, info_all, [VHostArg, ArgAtoms]},
++ ArgAtoms, [{timeout, Timeout}]);
+
+ action(list_bindings, Node, Args, Opts, Inform, Timeout) ->
+ Inform("Listing bindings", []),
+@@ -652,27 +670,31 @@ action(list_bindings, Node, Args, Opts, Inform, Timeout) ->
+ ArgAtoms = default_if_empty(Args, [source_name, source_kind,
+ destination_name, destination_kind,
+ routing_key, arguments]),
+- call(Node, {rabbit_binding, info_all, [VHostArg, ArgAtoms]},
+- ArgAtoms, Timeout);
++ call_emitter(Node, {rabbit_binding, info_all, [VHostArg, ArgAtoms]},
++ ArgAtoms, [{timeout, Timeout}]);
+
+ action(list_connections, Node, Args, _Opts, Inform, Timeout) ->
+ Inform("Listing connections", []),
+ ArgAtoms = default_if_empty(Args, [user, peer_host, peer_port, state]),
+- call(Node, {rabbit_networking, connection_info_all, [ArgAtoms]},
+- ArgAtoms, Timeout);
++ Nodes = nodes_in_cluster(Node, Timeout),
++ call_emitter(Node, {rabbit_networking, emit_connection_info_all, [Nodes, ArgAtoms]},
++ ArgAtoms, [{timeout, Timeout}, {chunks, length(Nodes)}]);
+
+ action(list_channels, Node, Args, _Opts, Inform, Timeout) ->
+ Inform("Listing channels", []),
+ ArgAtoms = default_if_empty(Args, [pid, user, consumer_count,
+ messages_unacknowledged]),
+- call(Node, {rabbit_channel, info_all, [ArgAtoms]},
+- ArgAtoms, Timeout);
++ Nodes = nodes_in_cluster(Node, Timeout),
++ call_emitter(Node, {rabbit_channel, emit_info_all, [Nodes, ArgAtoms]}, ArgAtoms,
++ [{timeout, Timeout}, {chunks, length(Nodes)}]);
+
+ action(list_consumers, Node, _Args, Opts, Inform, Timeout) ->
+ Inform("Listing consumers", []),
+ VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
+- call(Node, {rabbit_amqqueue, consumers_all, [VHostArg]},
+- rabbit_amqqueue:consumer_info_keys(), Timeout);
++ Nodes = nodes_in_cluster(Node, Timeout),
++ call_emitter(Node, {rabbit_amqqueue, emit_consumers_all, [Nodes, VHostArg]},
++ rabbit_amqqueue:consumer_info_keys(),
++ [{timeout, Timeout}, {chunks, length(Nodes)}]);
+
+ action(node_health_check, Node, _Args, _Opts, Inform, Timeout) ->
+ Inform("Checking health of node ~p", [Node]),
+@@ -788,17 +810,18 @@ display_info_message_row(IsEscaped, Result, InfoItemKeys) ->
+ {X, Value} -> Value
+ end, IsEscaped) || X <- InfoItemKeys]).
+
+-display_info_message(IsEscaped) ->
++display_info_message(IsEscaped, InfoItemKeys) ->
+ fun ([], _) ->
+ ok;
+- ([FirstResult|_] = List, InfoItemKeys) when is_list(FirstResult) ->
++ ([FirstResult|_] = List, _) when is_list(FirstResult) ->
+ lists:foreach(fun(Result) ->
+ display_info_message_row(IsEscaped, Result, InfoItemKeys)
+ end,
+ List),
+ ok;
+- (Result, InfoItemKeys) ->
+- display_info_message_row(IsEscaped, Result, InfoItemKeys)
++ (Result, _) ->
++ display_info_message_row(IsEscaped, Result, InfoItemKeys),
++ ok
+ end.
+
+ display_info_list(Results, InfoItemKeys) when is_list(Results) ->
+@@ -855,7 +878,10 @@ display_call_result(Node, MFA) ->
+ end.
+
+ unsafe_rpc(Node, Mod, Fun, Args) ->
+- case rpc_call(Node, Mod, Fun, Args) of
++ unsafe_rpc(Node, Mod, Fun, Args, ?RPC_TIMEOUT).
++
++unsafe_rpc(Node, Mod, Fun, Args, Timeout) ->
++ case rpc_call(Node, Mod, Fun, Args, Timeout) of
+ {badrpc, _} = Res -> throw(Res);
+ Normal -> Normal
+ end.
+@@ -874,33 +900,42 @@ ensure_app_running(Node) ->
+ call(Node, {Mod, Fun, Args}) ->
+ rpc_call(Node, Mod, Fun, lists:map(fun list_to_binary_utf8/1, Args)).
+
+-call(Node, {Mod, Fun, Args}, InfoKeys, Timeout) ->
+- call(Node, {Mod, Fun, Args}, InfoKeys, false, Timeout, false).
++call_emitter(Node, {Mod, Fun, Args}, InfoKeys, Opts) ->
++ Ref = start_emission(Node, {Mod, Fun, Args}, Opts),
++ display_emission_result(Ref, InfoKeys, Opts).
++
++start_emission(Node, {Mod, Fun, Args}, Opts) ->
++ ToBinUtf8 = proplists:get_value(to_bin_utf8, Opts, false),
++ Timeout = proplists:get_value(timeout, Opts, infinity),
++ Ref = proplists:get_value(ref, Opts, make_ref()),
++ rabbit_control_misc:spawn_emitter_caller(
++ Node, Mod, Fun, prepare_call_args(Args, ToBinUtf8),
++ Ref, self(), Timeout),
++ Ref.
++
++display_emission_result(Ref, InfoKeys, Opts) ->
++ IsEscaped = proplists:get_value(is_escaped, Opts, false),
++ Chunks = proplists:get_value(chunks, Opts, 1),
++ Timeout = proplists:get_value(timeout, Opts, infinity),
++ EmissionStatus = rabbit_control_misc:wait_for_info_messages(
++ self(), Ref, display_info_message(IsEscaped, InfoKeys), ok, Timeout, Chunks),
++ emission_to_action_result(EmissionStatus).
++
++%% Convert rabbit_control_misc:wait_for_info_messages/6 return value
++%% into form expected by rabbit_cli:main/3.
++emission_to_action_result({ok, ok}) ->
++ ok;
++emission_to_action_result({error, Error}) ->
++ Error.
+
+-call(Node, {Mod, Fun, Args}, InfoKeys, ToBinUtf8, Timeout) ->
+- call(Node, {Mod, Fun, Args}, InfoKeys, ToBinUtf8, Timeout, false).
++prepare_call_args(Args, ToBinUtf8) ->
++ case ToBinUtf8 of
++ true -> valid_utf8_args(Args);
++ false -> Args
++ end.
+
+-call(Node, {Mod, Fun, Args}, InfoKeys, ToBinUtf8, Timeout, IsEscaped) ->
+- Args0 = case ToBinUtf8 of
+- true -> lists:map(fun list_to_binary_utf8/1, Args);
+- false -> Args
+- end,
+- Ref = make_ref(),
+- Pid = self(),
+- spawn_link(
+- fun () ->
+- case rabbit_cli:rpc_call(Node, Mod, Fun, Args0,
+- Ref, Pid, Timeout) of
+- {error, _} = Error ->
+- Pid ! {error, Error};
+- {bad_argument, _} = Error ->
+- Pid ! {error, Error};
+- _ ->
+- ok
+- end
+- end),
+- rabbit_control_misc:wait_for_info_messages(
+- Pid, Ref, InfoKeys, display_info_message(IsEscaped), Timeout).
++valid_utf8_args(Args) ->
++ lists:map(fun list_to_binary_utf8/1, Args).
+
+ list_to_binary_utf8(L) ->
+ B = list_to_binary(L),
+@@ -950,7 +985,10 @@ split_list([_]) -> exit(even_list_needed);
+ split_list([A, B | T]) -> [{A, B} | split_list(T)].
+
+ nodes_in_cluster(Node) ->
+- unsafe_rpc(Node, rabbit_mnesia, cluster_nodes, [running]).
++ unsafe_rpc(Node, rabbit_mnesia, cluster_nodes, [running], ?RPC_TIMEOUT).
++
++nodes_in_cluster(Node, Timeout) ->
++ unsafe_rpc(Node, rabbit_mnesia, cluster_nodes, [running], Timeout).
+
+ alarms_by_node(Name) ->
+ case rpc_call(Name, rabbit, status, []) of