/* * * Copyright (c) 2013 - 2017 Lijun Liao * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the XiPKI software without * disclosing the source code of your own applications. * * For more information, please contact Lijun Liao at this * address: lijun.liao@gmail.com */ package org.xipki.commons.common; import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.xipki.commons.common.util.ParamUtil; import org.xipki.commons.common.util.StringUtil; /** * @author Lijun Liao * @since 2.0.0 */ public abstract class LoadExecutor { private static final String PROPKEY_LOADTEST = "org.xipki.loadtest"; private static final int DEFAULT_DURATION = 30; // 30 seconds private static final int DEFAULT_THREADS = 25; private boolean interrupted; private String description; private final ProcessLog processLog; private int duration = DEFAULT_DURATION; // in seconds private int threads = DEFAULT_THREADS; private AtomicLong errorAccount = new AtomicLong(0); private String unit = ""; public LoadExecutor(final String description) { this.description = ParamUtil.requireNonNull("description", description); this.processLog = new ProcessLog(0); } protected abstract Runnable getTestor() throws Exception; protected void shutdown() { } public void test() { System.getProperties().setProperty(PROPKEY_LOADTEST, "true"); List<Runnable> runnables = new ArrayList<>(threads); for (int i = 0; i < threads; i++) { Runnable runnable; try { runnable = getTestor(); } catch (Exception ex) { System.err.println("could not initialize Testor: " + ex.getMessage()); return; } runnables.add(runnable); } StringBuilder sb = new StringBuilder(); if (StringUtil.isNotBlank(description)) { sb.append(description); char ch = description.charAt(description.length() - 1); if (ch != '\n') { sb.append('\n'); } } sb.append("threads: ").append(threads).append("\n"); sb.append("duration: ").append(StringUtil.formatTime(duration, false)); System.out.println(sb.toString()); resetStartTime(); ExecutorService executor = Executors.newFixedThreadPool(threads); for (Runnable runnable : runnables) { executor.execute(runnable); } executor.shutdown(); printHeader(); while (true) { printStatus(); try { boolean terminated = executor.awaitTermination(1, TimeUnit.SECONDS); if (terminated) { break; } } catch (InterruptedException ex) { interrupted = true; } } printStatus(); printSummary(); shutdown(); System.getProperties().remove(PROPKEY_LOADTEST); } // method test public boolean isInterrupted() { return interrupted; } public void setDuration(final String duration) { ParamUtil.requireNonBlank("duration", duration); char unit = duration.charAt(duration.length() - 1); String numStr; if (unit == 's' || unit == 'm' || unit == 'h') { numStr = duration.substring(0, duration.length() - 1); } else { unit = 's'; numStr = duration; } int num; try { num = Integer.parseInt(numStr); } catch (NumberFormatException ex) { throw new IllegalArgumentException("invalid duration " + duration); } if (num < 1) { throw new IllegalArgumentException("invalid duration " + duration); } switch (unit) { case 's': this.duration = num; break; case 'm': this.duration = num * 60; break; case 'h': this.duration = num * 60 * 24; break; default: throw new RuntimeException("invalid duration unit " + unit); } } public void setThreads(final int threads) { if (threads > 0) { this.threads = threads; } } public long getErrorAccout() { return errorAccount.get(); } protected void account(final int all, final int failed) { processLog.addNumProcessed(all); errorAccount.addAndGet(failed); } protected void resetStartTime() { processLog.reset(); } protected boolean stop() { return interrupted || errorAccount.get() > 0 || System.currentTimeMillis() - processLog.getStartTime() >= duration * 1000L; } protected void printHeader() { processLog.printHeader(); } protected void printStatus() { processLog.printStatus(); } public void setUnit(final String unit) { this.unit = ParamUtil.requireNonNull("unit", unit); } protected void printSummary() { processLog.printTrailer(); final long account = processLog.getNumProcessed(); StringBuilder sb = new StringBuilder(); long elapsedTimeMs = processLog.getTotalElapsedTime(); sb.append("finished in " + StringUtil.formatTime(elapsedTimeMs / 1000, false) + "\n"); sb.append("account: " + account + " " + unit + "\n"); sb.append(" failed: " + errorAccount.get() + " " + unit + "\n"); sb.append("average: " + processLog.getTotalAverageSpeed() + " " + unit + "/s\n"); System.out.println(sb.toString()); } protected static long getSecureIndex() { SecureRandom random = new SecureRandom(); while (true) { long nextLong = random.nextLong(); if (nextLong > 0) { return nextLong; } } } }