/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.processor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.camel.ContextTestSupport;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
public class ThrottlerTest extends ContextTestSupport {
private static final int INTERVAL = 500;
private static final int TOLERANCE = 50;
private static final int MESSAGE_COUNT = 9;
protected boolean canTest() {
// skip test on windows as it does not run well there
return !isPlatform("windows");
}
public void testSendLotsOfMessagesButOnly3GetThroughWithin2Seconds() throws Exception {
if (!canTest()) {
return;
}
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
resultEndpoint.expectedMessageCount(3);
resultEndpoint.setResultWaitTime(2000);
for (int i = 0; i < MESSAGE_COUNT; i++) {
template.sendBody("seda:a", "<message>" + i + "</message>");
}
// lets pause to give the requests time to be processed
// to check that the throttle really does kick in
resultEndpoint.assertIsSatisfied();
}
public void testSendLotsOfMessagesWithRejectExecution() throws Exception {
if (!canTest()) {
return;
}
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
resultEndpoint.expectedMessageCount(2);
resultEndpoint.setResultWaitTime(2000);
MockEndpoint errorEndpoint = resolveMandatoryEndpoint("mock:error", MockEndpoint.class);
errorEndpoint.expectedMessageCount(4);
errorEndpoint.setResultWaitTime(2000);
for (int i = 0; i < 6; i++) {
template.sendBody("direct:start", "<message>" + i + "</message>");
}
// lets pause to give the requests time to be processed
// to check that the throttle really does kick in
resultEndpoint.assertIsSatisfied();
errorEndpoint.assertIsSatisfied();
}
public void testSendLotsOfMessagesSimultaneouslyButOnly3GetThrough() throws Exception {
if (!canTest()) {
return;
}
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
long elapsed = sendMessagesAndAwaitDelivery(MESSAGE_COUNT, "direct:a", MESSAGE_COUNT, resultEndpoint);
assertThrottlerTiming(elapsed, 1, INTERVAL, MESSAGE_COUNT);
}
public void testConfigurationWithConstantExpression() throws Exception {
if (!canTest()) {
return;
}
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
long elapsed = sendMessagesAndAwaitDelivery(MESSAGE_COUNT, "direct:expressionConstant", MESSAGE_COUNT, resultEndpoint);
assertThrottlerTiming(elapsed, 1, INTERVAL, MESSAGE_COUNT);
}
public void testConfigurationWithHeaderExpression() throws Exception {
if (!canTest()) {
return;
}
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
resultEndpoint.expectedMessageCount(MESSAGE_COUNT);
ExecutorService executor = Executors.newFixedThreadPool(MESSAGE_COUNT);
try {
sendMessagesWithHeaderExpression(executor, resultEndpoint, 1, INTERVAL, MESSAGE_COUNT);
} finally {
executor.shutdownNow();
}
}
public void testConfigurationWithChangingHeaderExpression() throws Exception {
if (!canTest()) {
return;
}
ExecutorService executor = Executors.newFixedThreadPool(5);
try {
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
sendMessagesWithHeaderExpression(executor, resultEndpoint, 1, INTERVAL, MESSAGE_COUNT);
Thread.sleep(INTERVAL + TOLERANCE); // sleep here to ensure the first throttle rate does not influence the next one.
resultEndpoint.reset();
sendMessagesWithHeaderExpression(executor, resultEndpoint, 10, INTERVAL, MESSAGE_COUNT);
Thread.sleep(INTERVAL + TOLERANCE); // sleep here to ensure the first throttle rate does not influence the next one.
resultEndpoint.reset();
sendMessagesWithHeaderExpression(executor, resultEndpoint, 1, INTERVAL, MESSAGE_COUNT);
Thread.sleep(INTERVAL + TOLERANCE); // sleep here to ensure the first throttle rate does not influence the next one.
resultEndpoint.reset();
sendMessagesWithHeaderExpression(executor, resultEndpoint, 10, INTERVAL, MESSAGE_COUNT);
} finally {
executor.shutdownNow();
}
}
public void testHighThrottleRate() throws Exception {
if (!canTest()) {
return;
}
int messageCount = 20000;
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
long elapsed = sendMessagesAndAwaitDelivery(messageCount, "direct:highThrottleRate", 5, resultEndpoint);
assertThrottlerTiming(elapsed, 1000, INTERVAL, messageCount);
}
private void assertThrottlerTiming(final long elapsedTimeMs, final int throttle, final int intervalMs, final int messageCount) {
// now assert that they have actually been throttled (use +/- 50 as slack)
long minimum = calculateMinimum(intervalMs, throttle, messageCount) - 50;
long maximum = calculateMaximum(intervalMs, throttle, messageCount) + 50;
log.info("Sent {} exchanges in {}ms, with throttle rate of {} per {}ms. Calculated min {}ms and max {}ms", new Object[]{messageCount, elapsedTimeMs, throttle, intervalMs, minimum, maximum});
assertTrue("Should take at least " + minimum + "ms, was: " + elapsedTimeMs, elapsedTimeMs >= minimum);
assertTrue("Should take at most " + maximum + "ms, was: " + elapsedTimeMs, elapsedTimeMs <= maximum + TOLERANCE);
}
private long sendMessagesAndAwaitDelivery(final int messageCount, final String endpointUri, final int threadPoolSize, final MockEndpoint receivingEndpoint) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
try {
if (receivingEndpoint != null) {
receivingEndpoint.expectedMessageCount(messageCount);
}
long start = System.nanoTime();
for (int i = 0; i < messageCount; i++) {
executor.execute(new Runnable() {
public void run() {
template.sendBody(endpointUri, "<message>payload</message>");
}
});
}
// let's wait for the exchanges to arrive
if (receivingEndpoint != null) {
receivingEndpoint.assertIsSatisfied();
}
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
} finally {
executor.shutdownNow();
}
}
private void sendMessagesWithHeaderExpression(final ExecutorService executor, final MockEndpoint resultEndpoint, final int throttle, final int intervalMs, final int messageCount)
throws InterruptedException {
resultEndpoint.expectedMessageCount(messageCount);
long start = System.nanoTime();
for (int i = 0; i < messageCount; i++) {
executor.execute(new Runnable() {
public void run() {
template.sendBodyAndHeader("direct:expressionHeader", "<message>payload</message>", "throttleValue", throttle);
}
});
}
// let's wait for the exchanges to arrive
resultEndpoint.assertIsSatisfied();
long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
assertThrottlerTiming(elapsed, throttle, intervalMs, messageCount);
}
private long calculateMinimum(final long periodMs, final long throttleRate, final long messageCount) {
if (messageCount % throttleRate > 0) {
return (long) Math.floor((double)messageCount / (double)throttleRate) * periodMs;
} else {
return (long) (Math.floor((double)messageCount / (double)throttleRate) * periodMs) - periodMs;
}
}
private long calculateMaximum(final long periodMs, final long throttleRate, final long messageCount) {
return ((long)Math.ceil((double)messageCount / (double)throttleRate)) * periodMs;
}
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
onException(ThrottlerRejectedExecutionException.class)
.handled(true)
.to("mock:error");
// START SNIPPET: ex
from("seda:a").throttle(3).timePeriodMillis(10000).to("log:result", "mock:result");
// END SNIPPET: ex
from("direct:a").throttle(1).timePeriodMillis(INTERVAL).to("log:result", "mock:result");
from("direct:expressionConstant").throttle(constant(1)).timePeriodMillis(INTERVAL).to("log:result", "mock:result");
from("direct:expressionHeader").throttle(header("throttleValue")).timePeriodMillis(INTERVAL).to("log:result", "mock:result");
from("direct:start").throttle(2).timePeriodMillis(10000).rejectExecution(true).to("log:result", "mock:result");
from("direct:highThrottleRate").throttle(1000).timePeriodMillis(INTERVAL).to("mock:result");
}
};
}
}