/* * 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.brooklyn.util.core.task; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.task.BasicExecutionManager; import org.apache.brooklyn.util.core.task.BasicTask; import org.apache.brooklyn.util.core.task.ScheduledTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.Predicate; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Callables; /** * Test the operation of the {@link BasicTask} class. * * TODO clarify test purpose */ public class BasicTaskExecutionPerformanceTest { private static final Logger log = LoggerFactory.getLogger(BasicTaskExecutionPerformanceTest.class); private static final int TIMEOUT_MS = 10*1000; private BasicExecutionManager em; public static final int MAX_OVERHEAD_MS = 1500; // was 750ms but saw 1.3s on buildhive public static final int EARLY_RETURN_GRACE = 25; // saw 13ms early return on jenkins! @BeforeMethod(alwaysRun=true) public void setUp() throws Exception { em = new BasicExecutionManager("mycontext"); } @AfterMethod(alwaysRun=true) public void tearDown() throws Exception { if (em != null) em.shutdownNow(); } @Test public void testScheduledTaskExecutedAfterDelay() throws Exception { int delay = 100; final CountDownLatch latch = new CountDownLatch(1); Callable<Task<?>> taskFactory = new Callable<Task<?>>() { @Override public Task<?> call() { return new BasicTask<Void>(new Runnable() { @Override public void run() { latch.countDown(); }}); }}; ScheduledTask t = new ScheduledTask(taskFactory).delay(delay); Stopwatch stopwatch = Stopwatch.createStarted(); em.submit(t); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); long actualDelay = stopwatch.elapsed(TimeUnit.MILLISECONDS); assertTrue(actualDelay > (delay-EARLY_RETURN_GRACE), "actualDelay="+actualDelay+"; delay="+delay); assertTrue(actualDelay < (delay+MAX_OVERHEAD_MS), "actualDelay="+actualDelay+"; delay="+delay); } @Test public void testScheduledTaskExecutedAtRegularPeriod() throws Exception { final int period = 100; final int numTimestamps = 4; final CountDownLatch latch = new CountDownLatch(1); final List<Long> timestamps = Collections.synchronizedList(Lists.<Long>newArrayList()); final Stopwatch stopwatch = Stopwatch.createStarted(); Callable<Task<?>> taskFactory = new Callable<Task<?>>() { @Override public Task<?> call() { return new BasicTask<Void>(new Runnable() { @Override public void run() { timestamps.add(stopwatch.elapsed(TimeUnit.MILLISECONDS)); if (timestamps.size() >= numTimestamps) latch.countDown(); }}); }}; ScheduledTask t = new ScheduledTask(taskFactory).delay(1).period(period); em.submit(t); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); synchronized (timestamps) { long prev = timestamps.get(0); for (long timestamp : timestamps.subList(1, timestamps.size())) { assertTrue(timestamp > prev+period-EARLY_RETURN_GRACE, "timestamps="+timestamps); assertTrue(timestamp < prev+period+MAX_OVERHEAD_MS, "timestamps="+timestamps); prev = timestamp; } } } @Test public void testCanCancelScheduledTask() throws Exception { final int period = 1; final long checkPeriod = 250; final List<Long> timestamps = Collections.synchronizedList(Lists.<Long>newArrayList()); Callable<Task<?>> taskFactory = new Callable<Task<?>>() { @Override public Task<?> call() { return new BasicTask<Void>(new Runnable() { @Override public void run() { timestamps.add(System.currentTimeMillis()); }}); }}; ScheduledTask t = new ScheduledTask(taskFactory).period(period); em.submit(t); t.cancel(); long cancelTime = System.currentTimeMillis(); int countImmediatelyAfterCancel = timestamps.size(); Thread.sleep(checkPeriod); int countWellAfterCancel = timestamps.size(); // should have at most 1 more execution after cancel log.info("testCanCancelScheduledTask saw "+countImmediatelyAfterCancel+" then cancel then "+countWellAfterCancel+" total"); assertTrue(countWellAfterCancel - countImmediatelyAfterCancel <= 2, "timestamps="+timestamps+"; cancelTime="+cancelTime); } // Previously, when we used a CopyOnWriteArraySet, performance for submitting new tasks was // terrible, and it degraded significantly as the number of previously executed tasks increased // (e.g. 9s for first 1000; 26s for next 1000; 42s for next 1000). @Test public void testExecutionManagerPerformance() throws Exception { // Was fixed at 1000 tasks, but was running out of virtual memory due to excessive thread creation // on machines which were not able to execute the threads quickly. final int NUM_TASKS = Math.min(500 * Runtime.getRuntime().availableProcessors(), 1000); final int NUM_TIMES = 10; final int MAX_ACCEPTABLE_TIME = 7500; // saw 5601ms on buildhive long tWarmup = execTasksAndWaitForDone(NUM_TASKS, ImmutableList.of("A")); List<Long> times = Lists.newArrayList(); for (int i = 1; i <= NUM_TIMES; i++) { times.add(execTasksAndWaitForDone(NUM_TASKS, ImmutableList.of("A"))); } Long toobig = Iterables.find( times, new Predicate<Long>() { public boolean apply(Long input) { return input > MAX_ACCEPTABLE_TIME; }}, null); assertNull(toobig, "warmup="+tWarmup+"; times="+times); } @SuppressWarnings({ "unchecked", "rawtypes" }) private long execTasksAndWaitForDone(int numTasks, List<?> tags) throws Exception { List<Task<?>> tasks = Lists.newArrayList(); long startTimestamp = System.currentTimeMillis(); for (int i = 1; i < numTasks; i++) { Task<?> t = new BasicTask(Callables.returning(null)); // no-op em.submit(MutableMap.of("tags", tags), t); tasks.add(t); } long submittedTimestamp = System.currentTimeMillis(); for (Task t : tasks) { t.get(); } long endTimestamp = System.currentTimeMillis(); long submitTime = submittedTimestamp - startTimestamp; long totalTime = endTimestamp - startTimestamp; log.info("Executed {} tasks; {}ms total; {}ms to submit", new Object[] {numTasks, totalTime, submitTime}); return totalTime; } }