├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── include └── rabbitmq_pulse_worker.hrl ├── package.mk └── src ├── rabbitmq_pulse.app.src ├── rabbitmq_pulse.erl ├── rabbitmq_pulse_exchange.erl ├── rabbitmq_pulse_lib.erl ├── rabbitmq_pulse_sup.erl └── rabbitmq_pulse_worker.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | .idea 3 | .DS_Store 4 | build 5 | dist 6 | ebin 7 | atlassian-ide-plugin.xml 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, MeetMe 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of the MeetMe nor the names of its contributors may be used 13 | to endorse or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 20 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 24 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include ../umbrella.mk 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RabbitMQ Pulse 2 | ============== 3 | RabbitMQ Pulse is an *experimental* exchange plugin for **RabbitMQ 3.0+** that publishes information made available by the 4 | rabbitmq-management plugin making cluster monitoring a push event instead of something you poll for. 5 | 6 | Messages can be published in JSON format as they would be received from the management API or they can be published 7 | in a format that is compatible with Graphite's carbon AMQP client, providing conversionless integration into Graphite 8 | and systems like rocksteady. 9 | 10 | This is a work in progress and is not meant for production systems (yet). 11 | 12 | Routing Overview 13 | ---------------- 14 | The *x-pulse* exchange type added by RabbitMQ Pulse creates a publishing exchange that will send status messages at pre-specified 15 | intervals to bound objects matching the routing key patterns it uses. 16 | 17 | The rabbitmq-pulse plugin will publish cluster, node and queue statistics to a topic *like* exchange with varying routing keys to 18 | allow for stats at multiple layers of granularity. For example, to get all stats for a server with a hostname of megabunny, 19 | you could bind to *.rabbit.megabunny. Or to get node stats for all hosts, you could do node.rabbit.*. RabbitMQ Pulse uses 20 | a similar style of routing-key behavior as the topic exchange, but without the partial word matching (ie no node.rab*.*). 21 | 22 | ### Example Routing Keys / Bindings 23 | 24 | "#" Receive all stats 25 | "overview" Receive only the overview stats 26 | "node.rabbit.*" Receive stats for all nodes in the cluster 27 | "node.rabbit.myhost" Receive stats only for the node "rabbit@myhost" 28 | "queue.#" Receive stats for all queues, but only queues 29 | "queue./.*" Receive stats for the / virtual host only 30 | "queue./.myqueue" Receive stats for myqueue in the / virtual host 31 | "queue.*.foo" Receive stats for all queues named "foo" in any virtual host 32 | 33 | Todo 34 | ---- 35 | - Add the ability to bind to get stats for: 36 | - Connections 37 | - Channels 38 | - Handle shutdown cleanly 39 | - Handle create and delete exchange properly 40 | 41 | Message Types 42 | ------------- 43 | - cluster overview: High-level cluster overview data 44 | - node stats: Per-node messages 45 | - queue stats: Per-queue messages 46 | 47 | Installation 48 | ------------ 49 | Currently, the best way to install the plugin is to follow the instructions at http://www.rabbitmq.com/plugin-development.html and 50 | setup the rabbitmq-public-umbrella checkout, clone the rabbitmq-pulse directory into it and custom compile the plugin. 51 | 52 | Plugin Configuration 53 | -------------------- 54 | Default configuration values: 55 | 56 | - default_username: guest 57 | - default_interval: 5000 58 | 59 | Interval is the number of miliseconds between publishing stats. To change the default configuration values, add a 60 | rabbitmq_pulse stanza in the rabbitmq.config file for the value you would like to override: 61 | 62 | [{rabbitmq_pulse, [{default_username, <<"guest">>}, 63 | {default_interval, 5000}, 64 | {default_format, <<"json">>]}] 65 | 66 | To override the default values, specify a username and/or interval as arguments when delcaring a x-pulse exchange. 67 | 68 | Per-exchange optional configuration values 69 | ------------------------------------------ 70 | 71 | - username: Username to connect to the vhost as 72 | - interval: Number of milliseconds between publishing of stats 73 | - format: The publishing format. Acceptable values are json or graphite. 74 | 75 | 76 | Examples 77 | -------- 78 | 79 | ### Cluster Overview Message (JSON) 80 | 81 | Exchange: rabbitmq-pulse 82 | Routing Key: overview 83 | 84 | Properties 85 | 86 | app_id: rabbitmq-pulse 87 | content_type: application/json 88 | delivery_mode: 1 89 | timestamp: 1351221662 90 | type: rabbitmq cluster overview 91 | 92 | Message: 93 | 94 | { 95 | "message_stats": { 96 | "publish": 8, 97 | "publish_details": { 98 | "interval": 5736915, 99 | "last_event": 1353718103779, 100 | "rate": 0.6972388470109807 101 | } 102 | }, 103 | "object_totals": { 104 | "channels": 1, 105 | "connections": 1, 106 | "consumers": 0, 107 | "exchanges": 9, 108 | "queues": 2 109 | }, 110 | "queue_totals": { 111 | "messages": 10, 112 | "messages_details": { 113 | "interval": 6321630, 114 | "last_event": 1353718104517, 115 | "rate": 0.8336426319843275 116 | }, 117 | "messages_ready": 10, 118 | "messages_ready_details": { 119 | "interval": 6321630, 120 | "last_event": 1353718104517, 121 | "rate": 0.8336426319843275 122 | }, 123 | "messages_unacknowledged": 0, 124 | "messages_unacknowledged_details": { 125 | "interval": 6321630, 126 | "last_event": 1353718104517, 127 | "rate": 0 128 | } 129 | } 130 | } 131 | 132 | ### Cluster Overview Single Stat Message (Graphite) 133 | 134 | Exchange: graphite 135 | Routing Key: rabbitmq_pulse.overview.rabbit.localhost.message_stats.deliver_details.rate 136 | 137 | Properties: 138 | 139 | app_id: rabbitmq-pulse 140 | content_type: application/json 141 | delivery_mode: 1 142 | timestamp: 1351748994 143 | type: rabbitmq-pulse cluster overview graphite datapoint 144 | 145 | Message: 146 | 147 | rabbitmq_pulse.overview.rabbit.localhost.message_stats.deliver_details.rate 945.8392531255008 1351748994 148 | 149 | ### Node Message (JSON) 150 | 151 | Exchange: rabbitmq-pulse 152 | Routing Key: node.rabbit.localhost 153 | 154 | Properties: 155 | 156 | app_id: rabbitmq-pulse 157 | content_type: application/json 158 | delivery_mode: 1 159 | timestamp: 1351221662 160 | type: rabbitmq node stats 161 | 162 | Message: 163 | 164 | { 165 | "contexts": [ 166 | { 167 | "description": "RabbitMQ Management", 168 | "path": "/", 169 | "port": 15672 170 | }, 171 | { 172 | "description": "Redirect to port 15672", 173 | "ignore_in_use": true, 174 | "path": "/", 175 | "port": 55672 176 | } 177 | ], 178 | "disk_free": 388873060352, 179 | "disk_free_alarm": false, 180 | "disk_free_limit": 1000000000, 181 | "fd_total": 256, 182 | "fd_used": 30, 183 | "mem_alarm": false, 184 | "mem_limit": 12796831334, 185 | "mem_used": 41054568, 186 | "name": "rabbit@localhost", 187 | "os_pid": "38160", 188 | "partitions": [], 189 | "proc_total": 1048576, 190 | "proc_used": 246, 191 | "run_queue": 0, 192 | "running": true, 193 | "sockets_total": 138, 194 | "sockets_used": 2, 195 | "type": "disc", 196 | "uptime": 12400 197 | } 198 | 199 | ### Queue Message (JSON) 200 | 201 | Exchange: rabbitmq-pulse 202 | Routing Key: queue./.test_two 203 | 204 | Properties: 205 | 206 | app_id: rabbitmq-pulse 207 | content_type: application/json 208 | delivery_mode: 1 209 | timestamp: 1353718351 210 | type: rabbitmq queue stats 211 | 212 | Message: 213 | 214 | { 215 | "policy": "", 216 | "exclusive_consumer_tag": "", 217 | "messages_ready": 0, 218 | "messages_unacknowledged": 0, 219 | "messages": 0, 220 | "consumers": 1, 221 | "active_consumers": 1, 222 | "memory": 89096, 223 | "backing_queue_status": { 224 | "q1": 0, 225 | "q2": 0, 226 | "delta": [ 227 | "delta", 228 | "undefined", 229 | 0, 230 | "undefined" 231 | ], 232 | "q3": 0, 233 | "q4": 0, 234 | "len": 0, 235 | "pending_acks": 0, 236 | "target_ram_count": "infinity", 237 | "ram_msg_count": 0, 238 | "ram_ack_count": 0, 239 | "next_seq_id": 25, 240 | "persistent_count": 0, 241 | "avg_ingress_rate": 0.19991921264616969, 242 | "avg_egress_rate": 0.19991921264616969, 243 | "avg_ack_ingress_rate": 0.19991921264616969, 244 | "avg_ack_egress_rate": 0.19991921264616969 245 | }, 246 | "messages_details": { 247 | "rate": 0, 248 | "interval": 5002982, 249 | "last_event": 1353718221808 250 | }, 251 | "messages_ready_details": { 252 | "rate": 0, 253 | "interval": 5002982, 254 | "last_event": 1353718221808 255 | }, 256 | "messages_unacknowledged_details": { 257 | "rate": 0, 258 | "interval": 5002982, 259 | "last_event": 1353718221808 260 | }, 261 | "incoming": [ 262 | { 263 | "stats": { 264 | "publish": 25, 265 | "publish_details": { 266 | "rate": 0.1999166347633037, 267 | "interval": 5002085, 268 | "last_event": 1353718221806 269 | } 270 | }, 271 | "exchange": { 272 | "name": "test", 273 | "vhost": "/" 274 | } 275 | } 276 | ], 277 | "deliveries": [ 278 | { 279 | "stats": { 280 | "ack": 15, 281 | "ack_details": { 282 | "rate": 0.19941320669797052, 283 | "interval": 5014713, 284 | "last_event": 1353718218329 285 | }, 286 | "deliver": 15, 287 | "deliver_details": { 288 | "rate": 0.19941320669797052, 289 | "interval": 5014713, 290 | "last_event": 1353718218329 291 | }, 292 | "deliver_get": 15, 293 | "deliver_get_details": { 294 | "rate": 0.19941320669797052, 295 | "interval": 5014713, 296 | "last_event": 1353718218329 297 | }, 298 | "redeliver": 11, 299 | "redeliver_details": { 300 | "rate": 0, 301 | "interval": 5014713, 302 | "last_event": 1353718218329 303 | } 304 | }, 305 | "channel_details": { 306 | "name": "127.0.0.1:61941 -> 127.0.0.1:5672 (1)", 307 | "number": 1, 308 | "connection_name": "127.0.0.1:61941 -> 127.0.0.1:5672", 309 | "peer_port": 61941, 310 | "peer_host": "127.0.0.1" 311 | } 312 | } 313 | ], 314 | "message_stats": { 315 | "ack": 15, 316 | "ack_details": { 317 | "rate": 0.19941320669797052, 318 | "interval": 5014713, 319 | "last_event": 1353718218329 320 | }, 321 | "deliver": 15, 322 | "deliver_details": { 323 | "rate": 0.19941320669797052, 324 | "interval": 5014713, 325 | "last_event": 1353718218329 326 | }, 327 | "deliver_get": 15, 328 | "deliver_get_details": { 329 | "rate": 0.19941320669797052, 330 | "interval": 5014713, 331 | "last_event": 1353718218329 332 | }, 333 | "redeliver": 11, 334 | "redeliver_details": { 335 | "rate": 0, 336 | "interval": 5014713, 337 | "last_event": 1353718218329 338 | }, 339 | "publish": 25, 340 | "publish_details": { 341 | "rate": 0.1999166347633037, 342 | "interval": 5002085, 343 | "last_event": 1353718221806 344 | } 345 | }, 346 | "consumer_details": [ 347 | { 348 | "channel_details": { 349 | "name": "127.0.0.1:61941 -> 127.0.0.1:5672 (1)", 350 | "number": 1, 351 | "connection_name": "127.0.0.1:61941 -> 127.0.0.1:5672", 352 | "peer_port": 61941, 353 | "peer_host": "127.0.0.1" 354 | }, 355 | "queue_details": { 356 | "name": "test_two", 357 | "vhost": "/" 358 | }, 359 | "consumer_tag": "ctag1.0", 360 | "exclusive": false, 361 | "ack_required": true 362 | } 363 | ], 364 | "name": "test_two", 365 | "vhost": "/", 366 | "durable": true, 367 | "auto_delete": false, 368 | "arguments": { 369 | "x-message-ttl": 60000 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /include/rabbitmq_pulse_worker.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% RabbitMQ Pulse Worker Records 3 | %% 4 | -record(rabbitmq_pulse_connection, {virtual_host, username, connection, channel, exchanges}). 5 | -record(rabbitmq_pulse_exchange, {virtual_host, username, exchange, interval, bindings, format, cluster}). 6 | -record(rabbitmq_pulse_state, {connections, exchanges}). 7 | -record(rabbitmq_queue_full, {memory, idle_since, policy, exclusive_consumer_pid, exclusive_consumer_tag, messages_ready, 8 | messages_unacknowledged, messages, consumers, active_consumers, slave_pids, 9 | synchronised_slave_pids, backing_queue_status, messages_details, messages_ready_details, 10 | messages_unacknowledged_details, incoming, consumer_details, name, vhost, durable, 11 | auto_delete, owner_pid, arguments, pid}). 12 | -------------------------------------------------------------------------------- /package.mk: -------------------------------------------------------------------------------- 1 | DEPS:=rabbitmq-server rabbitmq-erlang-client rabbitmq-management 2 | -------------------------------------------------------------------------------- /src/rabbitmq_pulse.app.src: -------------------------------------------------------------------------------- 1 | {application, rabbitmq_pulse, 2 | [{description, "RabbitMQ State Publishing Plugin"}, 3 | {vsn, "0.1.0"}, 4 | {modules, [rabbitmq_pulse_lib, 5 | rabbitmq_pulse_exchange, 6 | rabbitmq_pulse_sup, 7 | rabbitmq_pulse_worker]}, 8 | {registered, []}, 9 | {mod, {rabbitmq_pulse, []}}, 10 | {env, [{default_username, <<"guest">>}, 11 | {default_interval, 5000}]}, 12 | {applications, [kernel, stdlib, rabbit, rabbitmq_management, amqp_client]}]}. 13 | -------------------------------------------------------------------------------- /src/rabbitmq_pulse.erl: -------------------------------------------------------------------------------- 1 | -module(rabbitmq_pulse). 2 | 3 | -behaviour(application). 4 | 5 | -export([start/2, stop/1]). 6 | 7 | start(normal, []) -> 8 | rabbitmq_pulse_sup:start_link(). 9 | 10 | stop(_State) -> 11 | ok. 12 | -------------------------------------------------------------------------------- /src/rabbitmq_pulse_exchange.erl: -------------------------------------------------------------------------------- 1 | -module(rabbitmq_pulse_exchange). 2 | -author("gmr@meetme.com"). 3 | 4 | -include_lib("rabbit_common/include/rabbit.hrl"). 5 | 6 | -define(EXCHANGE_TYPE_BIN, <<"x-pulse">>). 7 | -define(TYPE, <<"type-module">>). 8 | 9 | -behaviour(rabbit_exchange_type). 10 | 11 | -rabbit_boot_step({?MODULE, 12 | [{description, "exchange type x-pulse"}, 13 | {mfa, {rabbit_registry, register, 14 | [exchange, ?EXCHANGE_TYPE_BIN, ?MODULE]}}, 15 | {requires, rabbit_registry}, 16 | {enables, kernel_ready}]}). 17 | 18 | -export([ 19 | add_binding/3, 20 | assert_args_equivalence/2, 21 | create/2, 22 | delete/3, 23 | policy_changed/3, 24 | description/0, 25 | remove_bindings/3, 26 | route/2, 27 | serialise_events/0, 28 | validate/1 29 | ]). 30 | 31 | add_binding(Tx, X, B) -> 32 | gen_server:cast(rabbitmq_pulse, {add_binding, Tx, X, B}), 33 | rabbit_exchange_type_topic:add_binding(Tx, X, B). 34 | 35 | assert_args_equivalence(X, Args) -> 36 | rabbit_exchange:assert_args_equivalence(X, Args). 37 | 38 | create(transaction, X) -> 39 | gen_server:cast(rabbitmq_pulse, {create, X}), 40 | ok; 41 | create(none, _X) -> 42 | ok. 43 | 44 | delete(transaction, X, _Bs) -> 45 | gen_server:cast(rabbitmq_pulse, {delete, X, _Bs}), 46 | ok; 47 | delete(none, _X, _Bs) -> 48 | ok. 49 | 50 | exchange_type(#exchange{arguments=Args}) -> 51 | case lists:keyfind(?TYPE, 1, Args) of 52 | {?TYPE, _, Type} -> 53 | case list_to_atom(binary_to_list(Type)) of 54 | rabbitmq_pulse_exchange -> 55 | error_logger:error_report("Cannot bind a Pulse exchange to a Pulse exchange"), 56 | rabbit_exchange_type_topic; 57 | Else -> Else 58 | end; 59 | _ -> rabbit_exchange_type_topic 60 | end. 61 | 62 | 63 | description() -> 64 | [{name, ?EXCHANGE_TYPE_BIN}, 65 | {description, <<"Experimental RabbitMQ-Pulse stats exchange">>}]. 66 | 67 | policy_changed(_Tx, _X1, _X2) -> 68 | gen_server:cast(rabbitmq_pulse, {policy_changed, _X1, _X2}), 69 | ok. 70 | 71 | remove_bindings(Tx, X, Bs) -> 72 | gen_server:cast(rabbitmq_pulse, {remove_bindings, Tx, X, Bs}), 73 | rabbit_exchange_type_topic:remove_bindings(Tx, X, Bs). 74 | 75 | route(X, Delivery) -> 76 | rabbit_exchange_type_topic:route(X, Delivery). 77 | 78 | serialise_events() -> 79 | false. 80 | 81 | validate(X) -> 82 | Exchange = exchange_type(X), 83 | Exchange:validate(X). 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/rabbitmq_pulse_lib.erl: -------------------------------------------------------------------------------- 1 | -module(rabbitmq_pulse_lib). 2 | 3 | -include_lib("amqp_client/include/amqp_client.hrl"). 4 | -include("rabbitmq_pulse_worker.hrl"). 5 | 6 | -export([add_binding/2, 7 | create_exchange/4, 8 | delete_exchange/4, 9 | establish_connections/1, 10 | process_interval/3, 11 | pulse_exchanges/0, 12 | remove_bindings/2]). 13 | 14 | -define(PREFIX, "rabbitmq_pulse"). 15 | -define(DEFAULT_INTERVAL, 5000). 16 | -define(IGNORE_KEYS, [applications, auth_mechanisms, erlang_version, exchange_types, processors, statistics_level, pid, 17 | owner_pid, exclusive_consumer_pid, slave_pids, synchronised_slave_pids]). 18 | -define(OVERVIEW_BINDINGS, [<<"#">>, <<"overview">>]). 19 | 20 | -define(OVERVIEW_TYPE, <<"rabbitmq cluster overview">>). 21 | -define(NODE_TYPE, <<"rabbitmq node stats">>). 22 | -define(QUEUE_TYPE, <<"rabbitmq queue stats">>). 23 | 24 | -define(GRAPHITE_OVERVIEW_TYPE, <<"rabbitmq-pulse cluster overview graphite datapoint">>). 25 | -define(GRAPHITE_NODE_TYPE, <<"rabbitmq-pulse node graphite datapoint">>). 26 | -define(GRAPHITE_QUEUE_TYPE, <<"rabbitmq-pulse queue graphite datapoint">>). 27 | 28 | %% General functions 29 | 30 | convert_gregorian_to_julian(GregorianSeconds) -> 31 | GregorianSeconds - 719528 * 24 * 3600. 32 | 33 | current_gregorian_timestamp() -> 34 | calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(now())). 35 | 36 | current_timestamp() -> 37 | convert_gregorian_to_julian(current_gregorian_timestamp()). 38 | 39 | get_env(EnvVar, DefaultValue) -> 40 | case application:get_env(rabbitmq_pulse, EnvVar) of 41 | undefined -> 42 | DefaultValue; 43 | {ok, V} -> 44 | V 45 | end. 46 | 47 | %% Functions for getting and filtering info from RabbitMQ configuration 48 | 49 | add_binding_to_exchange(Exchange, Source, RoutingKey) when Exchange#rabbitmq_pulse_exchange.exchange =:= Source -> 50 | Bindings = sets:to_list(sets:from_list(lists:append(Exchange#rabbitmq_pulse_exchange.bindings, [RoutingKey]))), 51 | #rabbitmq_pulse_exchange{virtual_host=Exchange#rabbitmq_pulse_exchange.virtual_host, 52 | username=Exchange#rabbitmq_pulse_exchange.username, 53 | exchange=Exchange#rabbitmq_pulse_exchange.exchange, 54 | interval=Exchange#rabbitmq_pulse_exchange.interval, 55 | format=Exchange#rabbitmq_pulse_exchange.format, 56 | cluster=Exchange#rabbitmq_pulse_exchange.cluster, 57 | bindings=Bindings}; 58 | 59 | add_binding_to_exchange(Exchange, _, _) -> 60 | Exchange. 61 | 62 | add_binding(Exchanges, {binding, {resource, _, exchange, Source}, RoutingKey, {resource,_,queue, _},_}) -> 63 | [add_binding_to_exchange(X, Source, RoutingKey) || X <- Exchanges]. 64 | 65 | add_startup_bindings(Exchanges) -> 66 | [get_exchange_bindings(Exchange) || Exchange <- Exchanges]. 67 | 68 | binding_exchange_and_routing_key([{source_name, Exchange}, {source_kind,exchange}, {destination_name,_}, {destination_kind,queue}, {routing_key,RoutingKey}, {arguments,_}]) -> 69 | {Exchange, RoutingKey}. 70 | 71 | create_exchange(Connections, Exchanges, VirtualHost, Exchange) -> 72 | rabbit_log:info('Create exchange: ~p~n~p~n', [VirtualHost, Exchange]), 73 | {Connections, Exchanges}. 74 | 75 | delete_exchange(Connections, Exchanges, VirtualHost, Exchange) -> 76 | rabbit_log:info('Delete exchange: ~p~n~p~n', [VirtualHost, Exchange]), 77 | {Connections, Exchanges}. 78 | 79 | distinct_vhost_pairs(Exchanges) -> 80 | lists:usort([{X#rabbitmq_pulse_exchange.virtual_host, X#rabbitmq_pulse_exchange.username} || X <- Exchanges]). 81 | 82 | filter_bindings(Exchange, Bindings) -> 83 | sets:to_list(sets:from_list(lists:flatten([RoutingKey || {BindingExchange, RoutingKey} <- remapped_bindings(Bindings), 84 | BindingExchange =:= Exchange]))). 85 | 86 | filter_pulse_exchange(Exchange={exchange,{resource, _, exchange, _}, 'x-pulse', _, _, _, _, _, _}) -> 87 | Exchange; 88 | 89 | filter_pulse_exchange({exchange,{resource, _, exchange, _}, _, _, _, _, _, _, _}) -> 90 | null. 91 | 92 | find_connection(Exchange, Connections)-> 93 | [Connection] = [C || C <- Connections, has_exchange(C, Exchange)], 94 | Connection. 95 | 96 | flatten(A, B, Subitem) when is_list(Subitem) -> 97 | lists:flatten([[A, B, C, D] || {C, D} <- Subitem]); 98 | 99 | flatten(A, B, Subitem) when is_number(Subitem) -> 100 | lists:flatten([A, B, Subitem]). 101 | 102 | get_all_exchanges() -> 103 | VirtualHosts = get_virtual_hosts(), 104 | get_exchanges(VirtualHosts). 105 | 106 | get_exchange_bindings(Exchange) -> 107 | #rabbitmq_pulse_exchange{virtual_host=Exchange#rabbitmq_pulse_exchange.virtual_host, 108 | username=Exchange#rabbitmq_pulse_exchange.username, 109 | exchange=Exchange#rabbitmq_pulse_exchange.exchange, 110 | interval=Exchange#rabbitmq_pulse_exchange.interval, 111 | format=Exchange#rabbitmq_pulse_exchange.format, 112 | cluster=Exchange#rabbitmq_pulse_exchange.cluster, 113 | bindings=get_bindings_from_rabbitmq(Exchange)}. 114 | 115 | get_bindings_from_rabbitmq(Exchange) -> 116 | filter_bindings(Exchange#rabbitmq_pulse_exchange.exchange, 117 | rabbit_binding:info_all(Exchange#rabbitmq_pulse_exchange.virtual_host)). 118 | 119 | get_binding_tuple(Value) -> 120 | list_to_tuple(get_binding_tuple(Value, [], [])). 121 | 122 | get_binding_tuple(<<>>, [], []) -> 123 | []; 124 | 125 | get_binding_tuple(<<>>, ReversedWord, ReversedRest) -> 126 | lists:reverse([lists:reverse(ReversedWord) | ReversedRest]); 127 | 128 | get_binding_tuple(<<$., Rest/binary>>, ReversedWord, ReversedRest) -> 129 | get_binding_tuple(Rest, [], [lists:reverse(ReversedWord) | ReversedRest]); 130 | 131 | get_binding_tuple(<>, ReversedWord, ReversedRest) -> 132 | get_binding_tuple(Rest, [C | ReversedWord], ReversedRest). 133 | 134 | get_channel(Exchange, Connections) -> 135 | Connection = find_connection(Exchange, Connections), 136 | Connection#rabbitmq_pulse_connection.channel. 137 | 138 | get_cluster_name(Args) -> 139 | case lists:keysearch(<<"cluster">>, 1, Args) of 140 | {value, Tuple} -> 141 | case Tuple of 142 | {_, longstr, Value} -> 143 | Value; 144 | {_, _, _} -> 145 | get_node_name() 146 | end; 147 | false -> 148 | get_node_name() 149 | end. 150 | 151 | get_exchanges([VirtualHost]) -> 152 | rabbit_exchange:list(VirtualHost). 153 | 154 | get_format(Args) -> 155 | case lists:keysearch(<<"format">>, 1, Args) of 156 | {value, Tuple} -> 157 | case Tuple of 158 | {_, longstr, <<"graphite">>} -> 159 | graphite; 160 | {_, longstr, <<"json">>} -> 161 | json; 162 | {_, longstr, _} -> 163 | json 164 | end; 165 | false -> 166 | json 167 | end. 168 | 169 | get_interval(Args) -> 170 | case lists:keysearch(<<"interval">>, 1, Args) of 171 | {value, Tuple} -> 172 | case Tuple of 173 | {_, longstr, Value} -> 174 | ListValue = bitstring_to_list(Value), 175 | {Integer, _} = string:to_integer(ListValue), 176 | case Integer of 177 | error -> 178 | {Float, _} = string:to_float(ListValue), 179 | case Float of 180 | error -> 181 | get_env(interval, ?DEFAULT_INTERVAL); 182 | _ -> 183 | Float 184 | end; 185 | _ -> 186 | Integer 187 | end; 188 | {_, integer, Value} -> 189 | Value; 190 | {_, float, Value} -> 191 | Value; 192 | {_, long, Value} -> 193 | Value 194 | end; 195 | false -> 196 | get_env(default_interval, ?DEFAULT_INTERVAL) 197 | end. 198 | 199 | get_kvp(Value) -> 200 | pair_up(lists:flatten([[[[flatten(A,D,E)] || {D,E} <- B]] || {A,B} <- Value])). 201 | 202 | get_node_name() -> 203 | string:join(string:tokens(atom_to_list(node(self())), "@"), "."). 204 | 205 | get_node_name(Node) -> 206 | case lists:keysearch(name, 1, Node) of 207 | {value, {name, Name}} -> 208 | atom_to_list(Name); 209 | false -> "unknown" 210 | end. 211 | 212 | get_queues() -> 213 | rabbit_mgmt_db:augment_queues([rabbit_mgmt_format:queue(Q) || Q <- rabbit_amqqueue:list()], full). 214 | 215 | get_node_routing_key(Node) -> 216 | Value = tuple_to_list(get_node_routing_key_tuple(get_node_name(Node))), 217 | iolist_to_binary(string:join(Value, ".")). 218 | 219 | get_node_routing_key_tuple(Value) -> 220 | list_to_tuple(lists:merge(["node"], string:tokens(Value, "@"))). 221 | 222 | get_queue_routing_key(Vhost, Name) -> 223 | iolist_to_binary(string:join(["queue", binary_to_list(Vhost), binary_to_list(Name)], ".")). 224 | 225 | get_username(Args) -> 226 | case lists:keysearch(<<"username">>, 1, Args) of 227 | {value, Tuple} -> 228 | {_, _, Username} = Tuple; 229 | false -> 230 | Username = get_env(default_username, <<"guest">>) 231 | end, 232 | Username. 233 | 234 | get_virtual_hosts() -> 235 | rabbit_vhost:list(). 236 | 237 | graphite__overview_key(Exchange, Key) -> 238 | string:join([?PREFIX, "overview", Exchange#rabbitmq_pulse_exchange.cluster, Key], "."). 239 | 240 | has_exchange(Connection, Exchange) -> 241 | lists:member(Exchange, Connection#rabbitmq_pulse_connection.exchanges). 242 | 243 | pair_up([A, B, C, D, E | Tail]) when is_atom(A), is_atom(B), is_atom(C), is_atom(D), is_number(E) -> 244 | [{string:join([atom_to_list(A), atom_to_list(B), atom_to_list(C), atom_to_list(D)], "."), E} | pair_up(Tail)]; 245 | 246 | pair_up([A, B, C, D | Tail]) when is_atom(A), is_atom(B), is_atom(C), is_number(D) -> 247 | [{string:join([atom_to_list(A), atom_to_list(B), atom_to_list(C)], "."), D} | pair_up(Tail)]; 248 | 249 | pair_up([A, B, C | Tail]) when is_atom(A), is_atom(B), is_number(C) -> 250 | [{string:join([atom_to_list(A), atom_to_list(B)], "."), C} | pair_up(Tail)]; 251 | 252 | pair_up([A, B | Tail]) when is_atom(A), is_number(B) -> 253 | [{atom_to_list(A), B} | pair_up(Tail)]; 254 | 255 | pair_up([]) -> 256 | []. 257 | 258 | pulse_exchanges() -> 259 | Exchanges = [remapped_exchange(X) || X <- get_all_exchanges(), filter_pulse_exchange(X) =/= null], 260 | add_startup_bindings(Exchanges). 261 | 262 | remapped_bindings(Bindings) -> 263 | [binding_exchange_and_routing_key(B) || B <- Bindings]. 264 | 265 | remapped_exchange(Exchange) -> 266 | {exchange,{resource, VirtualHost, exchange, Name}, 'x-pulse', _, _, _, Args, _, _} = Exchange, 267 | Remapped = #rabbitmq_pulse_exchange{virtual_host=VirtualHost, 268 | username=get_username(Args), 269 | exchange=Name, 270 | interval=get_interval(Args), 271 | format=get_format(Args), 272 | cluster=get_cluster_name(Args), 273 | bindings=[]}, 274 | Remapped. 275 | 276 | remapped_queue(Q) -> 277 | list_to_tuple([rabbitmq_queue_full |[proplists:get_value(X, Q) || X <- record_info(fields, rabbitmq_queue_full)]]). 278 | 279 | remove_binding_from_exchange(Exchange, Source, RoutingKey) when Exchange#rabbitmq_pulse_exchange.exchange =:= Source -> 280 | #rabbitmq_pulse_exchange{virtual_host=Exchange#rabbitmq_pulse_exchange.virtual_host, 281 | username=Exchange#rabbitmq_pulse_exchange.username, 282 | exchange=Exchange#rabbitmq_pulse_exchange.exchange, 283 | interval=Exchange#rabbitmq_pulse_exchange.interval, 284 | format=Exchange#rabbitmq_pulse_exchange.format, 285 | cluster=Exchange#rabbitmq_pulse_exchange.cluster, 286 | bindings=[Binding || Binding <- Exchange#rabbitmq_pulse_exchange.bindings, Binding =/= RoutingKey]}; 287 | 288 | remove_binding_from_exchange(Exchange, _, _) -> 289 | Exchange. 290 | 291 | remove_binding(Exchanges, {binding, {resource, _, exchange, Source}, RoutingKey, {resource,_,queue, _},_}) -> 292 | [remove_binding_from_exchange(X, Source, RoutingKey) || X <- Exchanges]. 293 | 294 | remove_bindings(Exchanges, Bindings) -> 295 | [Xs] = [remove_binding(Exchanges, Binding) || Binding <- Bindings], 296 | Xs. 297 | 298 | routing_key_match({BT, BN, BH}, {NT, NN, NH}) when BT =:= NT, BN =:= NN, BH =:= NH -> 299 | true; 300 | routing_key_match({BT, BN, BH}, {_NT, NN, NH}) when BT =:= "*", BN =:= NN, BH =:= NH -> 301 | true; 302 | routing_key_match({BT, BN, BH}, {NT, _NN, NH}) when BT =:= NT, BN =:= "*", BH =:= NH -> 303 | true; 304 | routing_key_match({BT, BN, BH}, {NT, NN, _NH}) when BT =:= NT, BN =:= NN, BH =:= "*" -> 305 | true; 306 | routing_key_match({BT, BN, BH}, {NT, NN, _NH}) when BT =:= NT, BN =:= NN, BH =:= "#" -> 307 | true; 308 | routing_key_match({BT, BN, BH}, {_NT, _NN, NH}) when BT =:= "*", BN =:= "*", BH =:= NH -> 309 | true; 310 | routing_key_match({BT, BN, BH}, {NT, _NN, _NH}) when BT =:= NT, BN =:= "*", BH =:= "*" -> 311 | true; 312 | routing_key_match({BT, BN, BH}, {NT, NN, _NH}) when BT =:= NT, BN =:= NN, BH =:= "#" -> 313 | true; 314 | routing_key_match({BT, BN}, {NT, _NN, _NH}) when BT =:= NT, BN =:= "#" -> 315 | true; 316 | routing_key_match({BT}, {_NT, _NN, _NH}) when BT =:= "#" -> 317 | true; 318 | routing_key_match(BT, {_NT, _NN, _NH}) when BT =:= "#" -> 319 | true; 320 | routing_key_match(_, _) -> 321 | false. 322 | 323 | %% Connection and AMQP specific functions 324 | 325 | add_exchange(Connection, Exchanges) -> 326 | [add_exchange_to_connection(Connection, Exchange) || Exchange <- Exchanges]. 327 | 328 | add_exchange_to_connection(Connection, Exchange) when Connection#rabbitmq_pulse_connection.virtual_host =:= Exchange#rabbitmq_pulse_exchange.virtual_host, 329 | Connection#rabbitmq_pulse_connection.username =:= Exchange#rabbitmq_pulse_exchange.username -> 330 | #rabbitmq_pulse_connection{connection=Connection#rabbitmq_pulse_connection.connection, 331 | channel=Connection#rabbitmq_pulse_connection.channel, 332 | virtual_host=Connection#rabbitmq_pulse_connection.virtual_host, 333 | username=Connection#rabbitmq_pulse_connection.username, 334 | exchanges=lists:append(Connection#rabbitmq_pulse_connection.exchanges, [Exchange#rabbitmq_pulse_exchange.exchange])}; 335 | 336 | add_exchange_to_connection(_, _) -> 337 | null. 338 | 339 | establish_connections(Exchanges) -> 340 | [Connections] = [add_exchange(open(VirtualHost, Username), Exchanges) || {VirtualHost, Username} <- distinct_vhost_pairs(Exchanges)], 341 | Connections. 342 | 343 | open(VirtualHost, Username) -> 344 | AdapterInfo = #amqp_adapter_info{name = <<"rabbitmq_pulse">>}, 345 | case amqp_connection:start(#amqp_params_direct{username = Username, 346 | virtual_host = VirtualHost, 347 | adapter_info = AdapterInfo}) of 348 | {ok, Connection} -> 349 | case amqp_connection:open_channel(Connection) of 350 | {ok, Channel} -> 351 | #rabbitmq_pulse_connection{virtual_host=VirtualHost, username=Username, connection=Connection, channel=Channel, exchanges=[]}; 352 | E -> 353 | catch amqp_connection:close(Connection), 354 | rabbit_log:warning("Error connecting to virtual host ~s as ~n: ~p~n", [VirtualHost, Username, E]), 355 | E 356 | end; 357 | E -> 358 | E 359 | end. 360 | 361 | publish_graphite_message(Channel, Exchange, Key, ValueString, Type) -> 362 | Epoch = current_timestamp(), 363 | [Timestamp] = io_lib:format("~p", [Epoch]), 364 | [Value] = io_lib:format("~p", [ValueString]), 365 | BasicPublish = #'basic.publish'{exchange=Exchange#rabbitmq_pulse_exchange.exchange, 366 | routing_key=list_to_bitstring(Key)}, 367 | Properties = #'P_basic'{app_id = <<"rabbitmq-pulse">>, 368 | content_type = <<"application/json">>, 369 | delivery_mode = 1, 370 | timestamp = Epoch, 371 | type = Type}, 372 | Message = list_to_bitstring(string:join([Key, Value, Timestamp], " ")), 373 | Content = #amqp_msg{props = Properties, payload = Message}, 374 | amqp_channel:call(Channel, BasicPublish, Content). 375 | 376 | publish_json_message(Channel, Exchange, RoutingKey, Message, Type) -> 377 | BasicPublish = #'basic.publish'{exchange=Exchange#rabbitmq_pulse_exchange.exchange, 378 | routing_key=RoutingKey}, 379 | Properties = #'P_basic'{app_id = <<"rabbitmq-pulse">>, 380 | content_type = <<"application/json">>, 381 | delivery_mode = 1, 382 | timestamp = current_timestamp(), 383 | type = Type}, 384 | Content = #amqp_msg{props = Properties, payload = Message}, 385 | amqp_channel:call(Channel, BasicPublish, Content). 386 | 387 | %% Poll functions 388 | 389 | build_stats_message(Node) -> 390 | Values = [{Key, Value} || {Key, Value} <- Node, not lists:member(Key, ?IGNORE_KEYS)], 391 | iolist_to_binary(mochijson2:encode(Values)). 392 | 393 | node_stats(Node) -> 394 | {get_node_routing_key(Node), build_stats_message(Node)}. 395 | 396 | queue_stats(Vhost, Name, Q) -> 397 | {get_queue_routing_key(Vhost, Name), build_stats_message(Q)}. 398 | 399 | process_binding_overview(Exchange, Channel) -> 400 | case length([true || Binding <- Exchange#rabbitmq_pulse_exchange.bindings, lists:member(Binding, ?OVERVIEW_BINDINGS)]) of 401 | 0 -> 402 | ok; 403 | _ -> 404 | process_overview(Exchange, Channel), 405 | ok 406 | end. 407 | 408 | process_interval(ExchangeName, Exchanges, Connections) -> 409 | [Exchange] = [Exchange || Exchange <- Exchanges, Exchange#rabbitmq_pulse_exchange.exchange =:= ExchangeName], 410 | Channel = get_channel(Exchange#rabbitmq_pulse_exchange.exchange, Connections), 411 | process_binding_overview(Exchange, Channel), 412 | [process_node(Channel, Exchange, N) || N <- rabbit_mgmt_wm_nodes:all_nodes()], 413 | [process_queue(Channel, Exchange, Q) || Q <- get_queues()], 414 | Exchange. 415 | 416 | process_node(Channel, Exchange, Node) when Exchange#rabbitmq_pulse_exchange.format =:= json -> 417 | case should_publish_node_stats(Exchange, Node) of 418 | true -> 419 | {RoutingKey, Message} = node_stats(Node), 420 | publish_json_message(Channel, Exchange, RoutingKey, Message, ?NODE_TYPE), 421 | ok; 422 | false -> 423 | ok 424 | end. 425 | 426 | process_overview(Exchange, Channel) when Exchange#rabbitmq_pulse_exchange.format =:= graphite -> 427 | Overview = rabbit_mgmt_db:get_overview(), 428 | Pairs = get_kvp(Overview), 429 | [publish_graphite_message(Channel, Exchange, graphite__overview_key(Exchange, Key), Value, ?GRAPHITE_OVERVIEW_TYPE) || {Key, Value} <- Pairs], 430 | ok; 431 | 432 | process_overview(Exchange, Channel) when Exchange#rabbitmq_pulse_exchange.format =:= json -> 433 | Overview = rabbit_mgmt_db:get_overview(), 434 | publish_json_message(Channel, Exchange, <<"overview">>, iolist_to_binary(mochijson2:encode(Overview)), ?OVERVIEW_TYPE), 435 | ok. 436 | 437 | process_queue(Channel, Exchange, Q) when Exchange#rabbitmq_pulse_exchange.format =:= json -> 438 | Queue = remapped_queue(Q), 439 | case should_publish_queue_stats(Exchange, Queue) of 440 | true -> 441 | {RoutingKey, Message} = queue_stats(Queue#rabbitmq_queue_full.vhost, Queue#rabbitmq_queue_full.name, Q), 442 | publish_json_message(Channel, Exchange, RoutingKey, Message, ?QUEUE_TYPE), 443 | ok 444 | end. 445 | 446 | should_publish_node_stats(Exchange, Node) -> 447 | NodeTuple = get_node_routing_key_tuple(get_node_name(Node)), 448 | lists:any(fun (V) -> V =:= true end, 449 | [routing_key_match(get_binding_tuple(Binding), NodeTuple) || Binding <- Exchange#rabbitmq_pulse_exchange.bindings]). 450 | 451 | should_publish_queue_stats(Exchange, Queue) -> 452 | QueueTuple = {"queue", binary_to_list(Queue#rabbitmq_queue_full.vhost), binary_to_list(Queue#rabbitmq_queue_full.name)}, 453 | lists:any(fun (V) -> V =:= true end, 454 | [routing_key_match(get_binding_tuple(Binding), QueueTuple) || Binding <- Exchange#rabbitmq_pulse_exchange.bindings]). 455 | -------------------------------------------------------------------------------- /src/rabbitmq_pulse_sup.erl: -------------------------------------------------------------------------------- 1 | -module(rabbitmq_pulse_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([start_link/0, init/1]). 6 | 7 | start_link() -> 8 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 9 | 10 | init([]) -> 11 | {ok, {{one_for_one, 3, 10}, 12 | [{rabbitmq_pulse_worker, 13 | {rabbitmq_pulse_worker, start_link, []}, 14 | permanent, 15 | 10000, 16 | worker, 17 | [rabbitmq_pulse_worker]} 18 | ]}}. 19 | -------------------------------------------------------------------------------- /src/rabbitmq_pulse_worker.erl: -------------------------------------------------------------------------------- 1 | -module(rabbitmq_pulse_worker). 2 | -behaviour(gen_server). 3 | 4 | -export([start_link/0]). 5 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 6 | -export([handle_interval/1]). 7 | 8 | -include_lib("amqp_client/include/amqp_client.hrl"). 9 | -include("rabbitmq_pulse_worker.hrl"). 10 | 11 | 12 | -define(IDLE_INTERVAL, 5000). 13 | 14 | start_timer(Duration, Exchange) -> 15 | timer:apply_after(Duration, ?MODULE, handle_interval, [Exchange]). 16 | 17 | start_exchange_timer(Exchange) -> 18 | start_timer(Exchange#rabbitmq_pulse_exchange.interval, 19 | Exchange#rabbitmq_pulse_exchange.exchange). 20 | 21 | start_exchange_timers(Exchanges) -> 22 | [start_exchange_timer(Exchange) || Exchange <- Exchanges]. 23 | 24 | %--------------------------- 25 | % Gen Server Implementation 26 | % -------------------------- 27 | start_link() -> 28 | gen_server:start_link({global, ?MODULE}, ?MODULE, [], []). 29 | 30 | init([]) -> 31 | register(rabbitmq_pulse, self()), 32 | Exchanges = rabbitmq_pulse_lib:pulse_exchanges(), 33 | Connections = rabbitmq_pulse_lib:establish_connections(Exchanges), 34 | start_exchange_timers(Exchanges), 35 | rabbit_log:info("rabbitmq-pulse initialized~n"), 36 | {ok, #rabbitmq_pulse_state{connections=Connections, exchanges=Exchanges}}. 37 | 38 | handle_call(_Msg, _From, _State) -> 39 | {noreply, unknown_command, _State}. 40 | 41 | handle_cast({handle_interval, ExchangeName}, State) -> 42 | Exchange = rabbitmq_pulse_lib:process_interval(ExchangeName, 43 | State#rabbitmq_pulse_state.exchanges, 44 | State#rabbitmq_pulse_state.connections), 45 | start_exchange_timer(Exchange), 46 | {noreply, State}; 47 | 48 | handle_cast({add_binding, none, _, Binding}, State) -> 49 | Exchanges = rabbitmq_pulse_lib:add_binding(State#rabbitmq_pulse_state.exchanges, Binding), 50 | NewState = #rabbitmq_pulse_state{connections=State#rabbitmq_pulse_state.connections, 51 | exchanges=Exchanges}, 52 | {noreply, NewState}; 53 | 54 | handle_cast({create, #exchange{name = #resource{virtual_host=VirtualHost, name=Name}, arguments = Args}}, State) -> 55 | {Connections, Exchanges} = rabbitmq_pulse_lib:create_exchange(State#rabbitmq_pulse_state.connections, 56 | State#rabbitmq_pulse_state.exchanges, 57 | VirtualHost, Name, Args), 58 | {noreply, #rabbitmq_pulse_state{connections=Connections, exchanges=Exchanges}}; 59 | 60 | handle_cast({delete, Exchange, _Bs}, State) -> 61 | {Connections, Exchanges} = rabbitmq_pulse_lib:delete_exchange(State#rabbitmq_pulse_state.connections, 62 | State#rabbitmq_pulse_state.exchanges, 63 | Exchange), 64 | rabbit_log:info("Post delete:~n~p~n~p~n", [Connections, Exchanges]), 65 | {noreply, #rabbitmq_pulse_state{connections=Connections, exchanges=Exchanges}}; 66 | 67 | handle_cast({policy_changed, _Tx, _X1, _X2}, State) -> 68 | rabbit_log:info("policy_changed: ~p, ~p, ~p ~p ~n", [_Tx, _X1, _X2, State]), 69 | {noreply, State}; 70 | 71 | handle_cast({recover, Exchange, Bs}, State) -> 72 | rabbit_log:info("recover: ~p ~p ~n", [Exchange, Bs, State]), 73 | {noreply, State}; 74 | 75 | handle_cast({remove_bindings, none, _, Bindings}, State) -> 76 | Exchanges = rabbitmq_pulse_lib:remove_bindings(State#rabbitmq_pulse_state.exchanges, Bindings), 77 | NewState = #rabbitmq_pulse_state{connections=State#rabbitmq_pulse_state.connections, 78 | exchanges=Exchanges}, 79 | {noreply, NewState}; 80 | 81 | handle_cast(_, State) -> 82 | {noreply, State}. 83 | 84 | handle_info(_Info, State) -> 85 | {noreply, State}. 86 | 87 | terminate(_, #rabbitmq_pulse_state{connections=_Connections}) -> 88 | % Close connections 89 | % amqp_channel:call(Channel, #'channel.close'{}), 90 | ok. 91 | 92 | code_change(_OldVsn, State, _Extra) -> 93 | {ok, State}. 94 | 95 | %--------------------------- 96 | 97 | handle_interval(Exchange) -> 98 | gen_server:cast({global, ?MODULE}, {handle_interval, Exchange}). 99 | 100 | --------------------------------------------------------------------------------