diff options
Diffstat (limited to 'src/main/java/bench/BenchRunner.java')
| -rw-r--r-- | src/main/java/bench/BenchRunner.java | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/src/main/java/bench/BenchRunner.java b/src/main/java/bench/BenchRunner.java new file mode 100644 index 0000000..5312266 --- /dev/null +++ b/src/main/java/bench/BenchRunner.java @@ -0,0 +1,153 @@ +package bench; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +public class BenchRunner { + private final BenchConfig config; + + public BenchRunner(BenchConfig config) { + this.config = config; + } + + public BenchResult run(String configName) throws Exception { + dropCaches(); + reconfigure(configName); + + Logger logger = LogManager.getLogger("bench"); + String message = config.generateMessage(); + int messageBytes = message.getBytes().length; + + CountDownLatch startLatch = new CountDownLatch(1); + AtomicBoolean running = new AtomicBoolean(true); + AtomicLong eventCounter = new AtomicLong(0); + AtomicLong targetEvents = new AtomicLong(config.getTotalEvents()); + + List<Thread> threads = new ArrayList<>(); + for (int i = 0; i < config.getThreads(); i++) { + Thread t = new Thread(new LogWorker( + logger, message, startLatch, running, eventCounter, targetEvents, config.getMode() + )); + t.start(); + threads.add(t); + } + + // Warmup phase + if (config.getWarmupSeconds() > 0) { + startLatch.countDown(); + Thread.sleep(config.getWarmupSeconds() * 1000); + eventCounter.set(0); + } else { + startLatch.countDown(); + } + + long startTime = System.nanoTime(); + + if (config.getMode() == BenchConfig.Mode.DURATION) { + Thread.sleep(config.getDurationSeconds() * 1000); + running.set(false); + } else { + while (eventCounter.get() < targetEvents.get()) { + Thread.sleep(10); + } + running.set(false); + } + + long endTime = System.nanoTime(); + + for (Thread t : threads) { + t.join(5000); + } + + long events = eventCounter.get(); + double durationSec = (endTime - startTime) / 1_000_000_000.0; + double eventsPerSec = events / durationSec; + double bytesPerSec = (events * messageBytes) / durationSec; + double mbPerSec = bytesPerSec / (1024 * 1024); + + // Shutdown log4j context to flush and release resources + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + ctx.stop(); + + return new BenchResult(configName, config.getThreads(), events, durationSec, eventsPerSec, mbPerSec); + } + + private void dropCaches() { + try { + ProcessBuilder pb = new ProcessBuilder("sh", "-c", "sync; echo 3 > /proc/sys/vm/drop_caches"); + pb.inheritIO(); + Process p = pb.start(); + int exitCode = p.waitFor(); + if (exitCode != 0) { + System.err.println("Warning: Failed to drop caches (exit code " + exitCode + "). Run as root for accurate benchmarks."); + } + } catch (Exception e) { + System.err.println("Warning: Could not drop caches: " + e.getMessage()); + } + } + + private void reconfigure(String configName) throws Exception { + // Fully shutdown existing context + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + ctx.stop(); + + // Set async ring buffer size via system property (must be set before reconfigure) + if (configName.startsWith("async-")) { + String size = configName.substring(6); // e.g., "1k", "4k", "10k" + int bufferSize = switch (size) { + case "1k" -> 1024; + case "4k" -> 4096; + case "10k" -> 10240; + default -> 4096; + }; + System.setProperty("log4j2.asyncLoggerRingBufferSize", String.valueOf(bufferSize)); + System.setProperty("log4j2.contextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"); + } else { + System.clearProperty("log4j2.asyncLoggerRingBufferSize"); + System.clearProperty("log4j2.contextSelector"); + } + + String resourcePath = "log4j2-" + configName + ".xml"; + URI uri = getClass().getClassLoader().getResource(resourcePath).toURI(); + Configurator.reconfigure(uri); + } + + public static class BenchResult { + public final String configName; + public final int threads; + public final long events; + public final double durationSec; + public final double eventsPerSec; + public final double mbPerSec; + + public BenchResult(String configName, int threads, long events, + double durationSec, double eventsPerSec, double mbPerSec) { + this.configName = configName; + this.threads = threads; + this.events = events; + this.durationSec = durationSec; + this.eventsPerSec = eventsPerSec; + this.mbPerSec = mbPerSec; + } + + public String toCsv() { + return String.format("%s,%d,%d,%.2f,%.0f,%.2f", + configName, threads, events, durationSec, eventsPerSec, mbPerSec); + } + + @Override + public String toString() { + return String.format("%-16s | %3d threads | %,12d events | %.2fs | %,.0f events/s | %.2f MB/s", + configName, threads, events, durationSec, eventsPerSec, mbPerSec); + } + } +} |
