All Downloads are FREE. Search and download functionalities are using the official Maven repository.

step.artefacts.handlers.loadtesting.Pacer Maven / Gradle / Ivy

There is a newer version: 3.27.0
Show newest version
package step.artefacts.handlers.loadtesting;

import java.util.function.Consumer;
import java.util.function.Predicate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import step.artefacts.handlers.CancellableSleep;
import step.core.execution.ExecutionContext;

public class Pacer {

	private static final int MAX_ACCUMULATIONS = 100;
	private static final Logger logger = LoggerFactory.getLogger(Pacer.class);

	public static void scheduleAtConstantRate(Consumer r, long executionsPerSecond, long maxDurationInSeconds, ExecutionContext executionContext) throws InterruptedException {
		scheduleAtConstantPacing(r, 1000/executionsPerSecond, maxDurationInSeconds, executionContext);
	
	}
	
	public static void scheduleAtConstantPacing(Consumer r, long pacingMs, long maxDurationInSeconds, ExecutionContext executionContext) throws InterruptedException {
		long maxDurationInMs = 1000 * maxDurationInSeconds;
		scheduleAtConstantPacing(r, pacingMs, c -> c.getDuration() < maxDurationInMs, executionContext);
	}
	
	public static class Context {
		
		private long duration;
		private long iterations;
		
		public boolean update(long duration, long iterations) {
			this.duration = duration;
			this.iterations = iterations;
			return true;
		}

		public long getDuration() {
			return duration;
		}

		public long getIterations() {
			return iterations;
		}
	}
	
	public static void scheduleAtConstantPacing(Consumer r, long pacingMs, Predicate predicate, ExecutionContext executionContext) throws InterruptedException {
		long start = System.currentTimeMillis();
		long pacingNs = pacingMs * 1000000;
		long accumulatedDelay = 0;
		int accumulationCounter = 0;
		
		long t2=System.nanoTime();
		long expectedSleepOfPreviousIterationNs=0;
		int iterationCount = 0;
		Context context = new Context();
		while (context.update(System.currentTimeMillis()-start, iterationCount++) && predicate.test(context)) {
			long actualSleepOfPreviousIterationNs = System.nanoTime()-t2;
			long sleepCorrection = actualSleepOfPreviousIterationNs-expectedSleepOfPreviousIterationNs;
			long t1 = System.nanoTime();
			r.accept(iterationCount);
			t2 = System.nanoTime();
			long duration = t2 - t1;
			long correctedDuration = duration + sleepCorrection;
			if (isDebugEnabled()) {
				debug("Executed in "+duration+"ns. Corrected duration "+correctedDuration+"ns");
			}
			long difference = pacingNs - correctedDuration;

			long sleepTime;
			if (difference < 0) {
				// Pacing exceeded => accumulating delay
				accumulatedDelay -= difference;
				accumulationCounter++;
				sleepTime = 0;
				if (isDebugEnabled()) {
					debug("Pacing exceeded. Accumulating " + -difference
							+ "ns of delay. Total accumulated delay: " + accumulatedDelay + "ns");
				}
			} else {
				// Execution faster than pacing => try to catch up
				sleepTime = Math.max(0, difference - accumulatedDelay);
				long caughtUpTime = difference - sleepTime;
				accumulatedDelay -= caughtUpTime;
				if (isDebugEnabled()) {
					debug("Execution " + difference + "ns faster than pacing. "
							+ (caughtUpTime > 0 ? "Caught " + caughtUpTime + "ns up." : "")+". Total accumulated delay: " + accumulatedDelay + "ns");
				}
			}

			if (accumulationCounter > 0 && accumulatedDelay == 0) {
				// Delay caught up. Reset accumulation counter
				accumulationCounter = 0;
				if (isDebugEnabled()) {
					debug("Delay caught up. Resetting accumulation counter");
				}
			}

			if (accumulationCounter > MAX_ACCUMULATIONS) {
				if (isDebugEnabled()) {
					debug("Accumulated delay during " + MAX_ACCUMULATIONS
							+ " consecutive iterations. Unlikely to catch up. Resetting accumulation counter");
				}
				accumulationCounter = 0;
				accumulatedDelay = 0;
			}
			
			long sleepTimeMs = sleepTime/1000000;
			if (isDebugEnabled()) {
				debug("Sleeping "+sleepTimeMs+"ms");
			}
			CancellableSleep.sleep(sleepTimeMs, executionContext::isInterrupted, Pacer.class);
			expectedSleepOfPreviousIterationNs = sleepTime;
		}
		
	}

	protected static boolean isDebugEnabled() {
		return logger.isDebugEnabled();
	}

	protected static void debug(String message) {
		logger.debug(message);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy