// Copyright (c) 2007-Present Pivotal Software, Inc. All rights reserved.
//
// This software, the RabbitMQ Java client library, is triple-licensed under the
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// info@rabbitmq.com.
package com.rabbitmq.client.test.functional;
import com.rabbitmq.client.*;
import com.rabbitmq.client.impl.StandardMetricsCollector;
import com.rabbitmq.client.impl.recovery.AutorecoveringConnection;
import com.rabbitmq.client.test.BrokerTestCase;
import com.rabbitmq.client.test.TestUtils;
import com.rabbitmq.tools.Host;
import org.awaitility.Duration;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import static org.awaitility.Awaitility.waitAtMost;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
*
*/
public class Metrics extends BrokerTestCase {
static final String QUEUE = "metrics.queue";
@Override
protected void createResources() throws IOException, TimeoutException {
channel.queueDeclare(QUEUE, true, false, false, null);
}
@Override
protected void releaseResources() throws IOException {
channel.queueDelete(QUEUE);
}
@Test public void metricsStandardConnection() throws IOException, TimeoutException {
doMetrics(createConnectionFactory());
}
@Test public void metricsAutoRecoveryConnection() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = createConnectionFactory();
connectionFactory.setAutomaticRecoveryEnabled(true);
doMetrics(connectionFactory);
}
private void doMetrics(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
StandardMetricsCollector metrics = new StandardMetricsCollector();
connectionFactory.setMetricsCollector(metrics);
Connection connection1 = null;
Connection connection2 = null;
try {
connection1 = connectionFactory.newConnection();
assertThat(metrics.getConnections().getCount(), is(1L));
connection1.createChannel();
connection1.createChannel();
Channel channel = connection1.createChannel();
assertThat(metrics.getChannels().getCount(), is(3L));
sendMessage(channel);
assertThat(metrics.getPublishedMessages().getCount(), is(1L));
sendMessage(channel);
assertThat(metrics.getPublishedMessages().getCount(), is(2L));
channel.basicGet(QUEUE, true);
assertThat(metrics.getConsumedMessages().getCount(), is(1L));
channel.basicGet(QUEUE, true);
assertThat(metrics.getConsumedMessages().getCount(), is(2L));
channel.basicGet(QUEUE, true);
assertThat(metrics.getConsumedMessages().getCount(), is(2L));
connection2 = connectionFactory.newConnection();
assertThat(metrics.getConnections().getCount(), is(2L));
connection2.createChannel();
channel = connection2.createChannel();
assertThat(metrics.getChannels().getCount(), is(3L+2L));
sendMessage(channel);
sendMessage(channel);
assertThat(metrics.getPublishedMessages().getCount(), is(2L+2L));
channel.basicGet(QUEUE, true);
assertThat(metrics.getConsumedMessages().getCount(), is(2L+1L));
channel.basicConsume(QUEUE, true, new DefaultConsumer(channel));
waitAtMost(timeout()).until(new ConsumedMessagesMetricsCallable(metrics), equalTo(2L+1L+1L));
safeClose(connection1);
waitAtMost(timeout()).until(new ConnectionsMetricsCallable(metrics), equalTo(1L));
waitAtMost(timeout()).until(new ChannelsMetricsCallable(metrics), equalTo(2L));
safeClose(connection2);
waitAtMost(timeout()).until(new ConnectionsMetricsCallable(metrics), equalTo(0L));
waitAtMost(timeout()).until(new ChannelsMetricsCallable(metrics), equalTo(0L));
assertThat(metrics.getAcknowledgedMessages().getCount(), is(0L));
assertThat(metrics.getRejectedMessages().getCount(), is(0L));
} finally {
safeClose(connection1);
safeClose(connection2);
}
}
@Test public void metricsAckStandardConnection() throws IOException, TimeoutException {
doMetricsAck(createConnectionFactory());
}
@Test public void metricsAckAutoRecoveryConnection() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = createConnectionFactory();
connectionFactory.setAutomaticRecoveryEnabled(true);
doMetricsAck(connectionFactory);
}
private void doMetricsAck(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
StandardMetricsCollector metrics = new StandardMetricsCollector();
connectionFactory.setMetricsCollector(metrics);
Connection connection = null;
try {
connection = connectionFactory.newConnection();
Channel channel1 = connection.createChannel();
Channel channel2 = connection.createChannel();
sendMessage(channel1);
GetResponse getResponse = channel1.basicGet(QUEUE, false);
channel1.basicAck(getResponse.getEnvelope().getDeliveryTag(), false);
assertThat(metrics.getConsumedMessages().getCount(), is(1L));
assertThat(metrics.getAcknowledgedMessages().getCount(), is(1L));
// basicGet / basicAck
sendMessage(channel1);
sendMessage(channel2);
sendMessage(channel1);
sendMessage(channel2);
sendMessage(channel1);
sendMessage(channel2);
GetResponse response1 = channel1.basicGet(QUEUE, false);
GetResponse response2 = channel2.basicGet(QUEUE, false);
GetResponse response3 = channel1.basicGet(QUEUE, false);
GetResponse response4 = channel2.basicGet(QUEUE, false);
GetResponse response5 = channel1.basicGet(QUEUE, false);
GetResponse response6 = channel2.basicGet(QUEUE, false);
assertThat(metrics.getConsumedMessages().getCount(), is(1L+6L));
assertThat(metrics.getAcknowledgedMessages().getCount(), is(1L));
channel1.basicAck(response5.getEnvelope().getDeliveryTag(), false);
assertThat(metrics.getAcknowledgedMessages().getCount(), is(1L+1L));
channel1.basicAck(response3.getEnvelope().getDeliveryTag(), true);
assertThat(metrics.getAcknowledgedMessages().getCount(), is(1L+1L+2L));
channel2.basicAck(response2.getEnvelope().getDeliveryTag(), true);
assertThat(metrics.getAcknowledgedMessages().getCount(), is(1L+(1L+2L)+1L));
channel2.basicAck(response6.getEnvelope().getDeliveryTag(), true);
assertThat(metrics.getAcknowledgedMessages().getCount(), is(1L+(1L+2L)+1L+2L));
long alreadySentMessages = 1+(1+2)+1+2;
// basicConsume / basicAck
channel1.basicConsume(QUEUE, false, new MultipleAckConsumer(channel1, false));
channel1.basicConsume(QUEUE, false, new MultipleAckConsumer(channel1, true));
channel2.basicConsume(QUEUE, false, new MultipleAckConsumer(channel2, false));
channel2.basicConsume(QUEUE, false, new MultipleAckConsumer(channel2, true));
int nbMessages = 10;
for(int i = 0; i < nbMessages; i++) {
sendMessage(i%2 == 0 ? channel1 : channel2);
}
waitAtMost(timeout()).until(
new ConsumedMessagesMetricsCallable(metrics),
equalTo(alreadySentMessages+nbMessages)
);
waitAtMost(timeout()).until(
new AcknowledgedMessagesMetricsCallable(metrics),
equalTo(alreadySentMessages+nbMessages)
);
} finally {
safeClose(connection);
}
}
@Test public void metricsRejectStandardConnection() throws IOException, TimeoutException {
doMetricsReject(createConnectionFactory());
}
@Test public void metricsRejectAutoRecoveryConnection() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = createConnectionFactory();
connectionFactory.setAutomaticRecoveryEnabled(true);
doMetricsReject(connectionFactory);
}
private void doMetricsReject(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
StandardMetricsCollector metrics = new StandardMetricsCollector();
connectionFactory.setMetricsCollector(metrics);
Connection connection = null;
try {
connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
sendMessage(channel);
sendMessage(channel);
sendMessage(channel);
GetResponse response1 = channel.basicGet(QUEUE, false);
GetResponse response2 = channel.basicGet(QUEUE, false);
GetResponse response3 = channel.basicGet(QUEUE, false);
channel.basicReject(response2.getEnvelope().getDeliveryTag(), false);
assertThat(metrics.getRejectedMessages().getCount(), is(1L));
channel.basicNack(response3.getEnvelope().getDeliveryTag(), true, false);
assertThat(metrics.getRejectedMessages().getCount(), is(1L+2L));
} finally {
safeClose(connection);
}
}
@Test public void multiThreadedMetricsStandardConnection() throws InterruptedException, TimeoutException, IOException {
doMultiThreadedMetrics(createConnectionFactory());
}
@Test public void multiThreadedMetricsAutoRecoveryConnection() throws InterruptedException, TimeoutException, IOException {
ConnectionFactory connectionFactory = createConnectionFactory();
connectionFactory.setAutomaticRecoveryEnabled(true);
doMultiThreadedMetrics(connectionFactory);
}
private void doMultiThreadedMetrics(ConnectionFactory connectionFactory) throws IOException, TimeoutException, InterruptedException {
StandardMetricsCollector metrics = new StandardMetricsCollector();
connectionFactory.setMetricsCollector(metrics);
int nbConnections = 3;
int nbChannelsPerConnection = 5;
int nbChannels = nbConnections * nbChannelsPerConnection;
long nbOfMessages = 100;
int nbTasks = nbChannels; // channel are not thread-safe
Random random = new Random();
// create connections
Connection [] connections = new Connection[nbConnections];
ExecutorService executorService = Executors.newFixedThreadPool(nbTasks);
try {
Channel [] channels = new Channel[nbChannels];
for(int i = 0; i < nbConnections; i++) {
connections[i] = connectionFactory.newConnection();
for(int j = 0; j < nbChannelsPerConnection; j++) {
Channel channel = connections[i].createChannel();
channel.basicQos(1);
channels[i * nbChannelsPerConnection + j] = channel;
}
}
// consume messages without ack
for(int i = 0; i < nbOfMessages; i++) {
sendMessage(channels[random.nextInt(nbChannels)]);
}
List<Callable<Void>> tasks = new ArrayList<Callable<Void>>();
for(int i = 0; i < nbTasks; i++) {
Channel channelForConsuming = channels[random.nextInt(nbChannels)];
tasks.add(random.nextInt(10)%2 == 0 ?
new BasicGetTask(channelForConsuming, true) :
new BasicConsumeTask(channelForConsuming, true));
}
executorService.invokeAll(tasks);
assertThat(metrics.getPublishedMessages().getCount(), is(nbOfMessages));
waitAtMost(timeout()).until(new ConsumedMessagesMetricsCallable(metrics), equalTo(nbOfMessages));
assertThat(metrics.getAcknowledgedMessages().getCount(), is(0L));
// to remove the listeners
for(int i = 0; i < nbChannels; i++) {
channels[i].close();
Channel channel = connections[random.nextInt(nbConnections)].createChannel();
channel.basicQos(1);
channels[i] = channel;
}
// consume messages with ack
for(int i = 0; i < nbOfMessages; i++) {
sendMessage(channels[random.nextInt(nbChannels)]);
}
executorService.shutdownNow();
executorService = Executors.newFixedThreadPool(nbTasks);
tasks = new ArrayList<Callable<Void>>();
for(int i = 0; i < nbTasks; i++) {
Channel channelForConsuming = channels[i];
tasks.add(random.nextBoolean() ?
new BasicGetTask(channelForConsuming, false) :
new BasicConsumeTask(channelForConsuming, false));
}
executorService.invokeAll(tasks);
assertThat(metrics.getPublishedMessages().getCount(), is(2*nbOfMessages));
waitAtMost(timeout()).until(new ConsumedMessagesMetricsCallable(metrics), equalTo(2*nbOfMessages));
waitAtMost(timeout()).until(new AcknowledgedMessagesMetricsCallable(metrics), equalTo(nbOfMessages));
// to remove the listeners
for(int i = 0; i < nbChannels; i++) {
channels[i].close();
Channel channel = connections[random.nextInt(nbConnections)].createChannel();
channel.basicQos(1);
channels[i] = channel;
}
// consume messages and reject them
for(int i = 0; i < nbOfMessages; i++) {
sendMessage(channels[random.nextInt(nbChannels)]);
}
executorService.shutdownNow();
executorService = Executors.newFixedThreadPool(nbTasks);
tasks = new ArrayList<Callable<Void>>();
for(int i = 0; i < nbTasks; i++) {
Channel channelForConsuming = channels[i];
tasks.add(random.nextBoolean() ?
new BasicGetRejectTask(channelForConsuming) :
new BasicConsumeRejectTask(channelForConsuming));
}
executorService.invokeAll(tasks);
assertThat(metrics.getPublishedMessages().getCount(), is(3*nbOfMessages));
waitAtMost(timeout()).until(new ConsumedMessagesMetricsCallable(metrics), equalTo(3*nbOfMessages));
waitAtMost(timeout()).until(new AcknowledgedMessagesMetricsCallable(metrics), equalTo(nbOfMessages));
waitAtMost(timeout()).until(new RejectedMessagesMetricsCallable(metrics), equalTo(nbOfMessages));
} finally {
for (Connection connection : connections) {
safeClose(connection);
}
executorService.shutdownNow();
}
}
@Test public void errorInChannelStandardConnection() throws IOException, TimeoutException {
errorInChannel(createConnectionFactory());
}
@Test public void errorInChananelAutoRecoveryConnection() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = createConnectionFactory();
connectionFactory.setAutomaticRecoveryEnabled(true);
errorInChannel(connectionFactory);
}
private void errorInChannel(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
StandardMetricsCollector metrics = new StandardMetricsCollector();
connectionFactory.setMetricsCollector(metrics);
Connection connection = null;
try {
connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
assertThat(metrics.getConnections().getCount(), is(1L));
assertThat(metrics.getChannels().getCount(), is(1L));
channel.basicPublish("unlikelynameforanexchange", "", null, "msg".getBytes("UTF-8"));
waitAtMost(timeout()).until(new ChannelsMetricsCallable(metrics), is(0L));
assertThat(metrics.getConnections().getCount(), is(1L));
} finally {
safeClose(connection);
}
}
@Test public void checkListenersWithAutoRecoveryConnection() throws Exception {
ConnectionFactory connectionFactory = createConnectionFactory();
connectionFactory.setNetworkRecoveryInterval(2000);
connectionFactory.setAutomaticRecoveryEnabled(true);
StandardMetricsCollector metrics = new StandardMetricsCollector();
connectionFactory.setMetricsCollector(metrics);
Connection connection = null;
try {
connection = connectionFactory.newConnection();
Collection<?> shutdownHooks = getShutdownHooks(connection);
assertThat(shutdownHooks.size(), is(0));
connection.createChannel();
assertThat(metrics.getConnections().getCount(), is(1L));
assertThat(metrics.getChannels().getCount(), is(1L));
closeAndWaitForRecovery((AutorecoveringConnection) connection);
assertThat(metrics.getConnections().getCount(), is(1L));
assertThat(metrics.getChannels().getCount(), is(1L));
assertThat(shutdownHooks.size(), is(0));
} finally {
safeClose(connection);
}
}
private ConnectionFactory createConnectionFactory() {
ConnectionFactory connectionFactory = TestUtils.connectionFactory();
return connectionFactory;
}
private void closeAndWaitForRecovery(AutorecoveringConnection connection) throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
connection.addRecoveryListener(new RecoveryListener() {
public void handleRecovery(Recoverable recoverable) {
latch.countDown();
}
@Override
public void handleRecoveryStarted(Recoverable recoverable) {
// no-op
}
});
Host.closeConnection(connection);
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
private Collection<?> getShutdownHooks(Connection connection) throws NoSuchFieldException, IllegalAccessException {
Field shutdownHooksField = connection.getClass().getDeclaredField("shutdownHooks");
shutdownHooksField.setAccessible(true);
return (Collection<?>) shutdownHooksField.get(connection);
}
private static class BasicGetTask implements Callable<Void> {
final Channel channel;
final boolean autoAck;
final Random random = new Random();
private BasicGetTask(Channel channel, boolean autoAck) {
this.channel = channel;
this.autoAck = autoAck;
}
@Override
public Void call() throws Exception {
GetResponse getResponse = this.channel.basicGet(QUEUE, autoAck);
if(!autoAck) {
channel.basicAck(getResponse.getEnvelope().getDeliveryTag(), random.nextBoolean());
}
return null;
}
}
private static class BasicConsumeTask implements Callable<Void> {
final Channel channel;
final boolean autoAck;
final Random random = new Random();
private BasicConsumeTask(Channel channel, boolean autoAck) {
this.channel = channel;
this.autoAck = autoAck;
}
@Override
public Void call() throws Exception {
this.channel.basicConsume(QUEUE, autoAck, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
if(!autoAck) {
getChannel().basicAck(envelope.getDeliveryTag(), random.nextBoolean());
}
}
});
return null;
}
}
private static class BasicGetRejectTask implements Callable<Void> {
final Channel channel;
final Random random = new Random();
private BasicGetRejectTask(Channel channel) {
this.channel = channel;
}
@Override
public Void call() throws Exception {
GetResponse response = channel.basicGet(QUEUE, false);
if(response != null) {
if(random.nextBoolean()) {
channel.basicNack(response.getEnvelope().getDeliveryTag(), random.nextBoolean(), false);
} else {
channel.basicReject(response.getEnvelope().getDeliveryTag(), false);
}
}
return null;
}
}
private static class BasicConsumeRejectTask implements Callable<Void> {
final Channel channel;
final Random random = new Random();
private BasicConsumeRejectTask(Channel channel) {
this.channel = channel;
}
@Override
public Void call() throws Exception {
this.channel.basicConsume(QUEUE, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
if(random.nextBoolean()) {
channel.basicNack(envelope.getDeliveryTag(), random.nextBoolean(), false);
} else {
channel.basicReject(envelope.getDeliveryTag(), false);
}
}
});
return null;
}
}
private void safeClose(Connection connection) {
if(connection != null) {
try {
connection.abort();
} catch (Exception e) {
// OK
}
}
}
private void sendMessage(Channel channel) throws IOException {
channel.basicPublish("", QUEUE, null, "msg".getBytes("UTF-8"));
}
private Duration timeout() {
return new Duration(10, TimeUnit.SECONDS);
}
private static class MultipleAckConsumer extends DefaultConsumer {
final boolean multiple;
public MultipleAckConsumer(Channel channel, boolean multiple) {
super(channel);
this.multiple = multiple;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
throw new RuntimeException("Error during randomized wait",e);
}
getChannel().basicAck(envelope.getDeliveryTag(), multiple);
}
}
static abstract class MetricsCallable implements Callable<Long> {
final StandardMetricsCollector metrics;
protected MetricsCallable(StandardMetricsCollector metrics) {
this.metrics = metrics;
}
}
static class ConnectionsMetricsCallable extends MetricsCallable {
ConnectionsMetricsCallable(StandardMetricsCollector metrics) {
super(metrics);
}
@Override
public Long call() throws Exception {
return metrics.getConnections().getCount();
}
}
static class ChannelsMetricsCallable extends MetricsCallable {
ChannelsMetricsCallable(StandardMetricsCollector metrics) {
super(metrics);
}
@Override
public Long call() throws Exception {
return metrics.getChannels().getCount();
}
}
static class PublishedMessagesMetricsCallable extends MetricsCallable {
PublishedMessagesMetricsCallable(StandardMetricsCollector metrics) {
super(metrics);
}
@Override
public Long call() throws Exception {
return metrics.getPublishedMessages().getCount();
}
}
static class ConsumedMessagesMetricsCallable extends MetricsCallable {
ConsumedMessagesMetricsCallable(StandardMetricsCollector metrics) {
super(metrics);
}
@Override
public Long call() throws Exception {
return metrics.getConsumedMessages().getCount();
}
}
static class AcknowledgedMessagesMetricsCallable extends MetricsCallable {
AcknowledgedMessagesMetricsCallable(StandardMetricsCollector metrics) {
super(metrics);
}
@Override
public Long call() throws Exception {
return metrics.getAcknowledgedMessages().getCount();
}
}
static class RejectedMessagesMetricsCallable extends MetricsCallable {
RejectedMessagesMetricsCallable(StandardMetricsCollector metrics) {
super(metrics);
}
@Override
public Long call() throws Exception {
return metrics.getRejectedMessages().getCount();
}
}
}