/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.manual; import junit.framework.TestCase; import password.pwm.AppProperty; import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.stored.ConfigurationReader; import password.pwm.svc.stats.EventRateMeter; import password.pwm.util.java.FileSystemUtility; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.Percent; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.localdb.LocalDB; import password.pwm.util.localdb.LocalDBFactory; import password.pwm.util.logging.LocalDBLogger; import password.pwm.util.logging.LocalDBLoggerSettings; import password.pwm.util.logging.PwmLogEvent; import password.pwm.util.logging.PwmLogLevel; import password.pwm.util.secure.PwmRandom; import java.io.File; import java.io.Serializable; import java.math.RoundingMode; import java.text.NumberFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Random; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class LocalDBLoggerTest extends TestCase { private final NumberFormat numberFormat = NumberFormat.getNumberInstance(); private LocalDBLogger localDBLogger; private LocalDB localDB; private Configuration config; private final AtomicInteger eventsAdded = new AtomicInteger(0); private EventRateMeter eventRateMeter = new EventRateMeter(new TimeDuration(60 * 1000)); private Settings settings; private Date startTime; @Override protected void setUp() throws Exception { super.setUp(); //To change body of overridden methods use File | Settings | File Templates. TestHelper.setupLogging(); final File localDBPath = new File(TestHelper.getParameter("localDBPath")); final File configFile = new File(TestHelper.getParameter("configurationFile")); final ConfigurationReader reader = new ConfigurationReader(configFile); config = reader.getConfiguration(); localDB = LocalDBFactory.getInstance( localDBPath, false, null, config ); //localDB.truncate(LocalDB.DB.EVENTLOG_EVENTS); //System.out.println(localDB.size(LocalDB.DB.EVENTLOG_EVENTS)); //new TimeDuration(1,TimeUnit.HOURS).pause(); { // open localDBLogger based on configuration settings; final int maxEvents = (int) reader.getConfiguration().readSettingAsLong(PwmSetting.EVENTS_PWMDB_MAX_EVENTS); final long maxAgeMs = reader.getConfiguration().readSettingAsLong(PwmSetting.EVENTS_PWMDB_MAX_AGE) * (long) 1000; final LocalDBLoggerSettings settings = new LocalDBLoggerSettings.Builder().setMaxEvents(maxEvents).setMaxAge(new TimeDuration(maxAgeMs)).setFlags(Collections.<LocalDBLoggerSettings.Flag>emptySet()).createLocalDBLoggerSettings(); localDBLogger = new LocalDBLogger(null, localDB, settings); } settings = new Settings(); settings.threads = 10; settings.testDuration = new TimeDuration(3, TimeUnit.HOURS); settings.valueLength = 5000; settings.batchSize = 100; } private void out(String output) { System.out.println(JavaHelper.toIsoDate(new Date())+ " " + output); } public void testBulkAddEvents() throws InterruptedException { out("starting bulk add... "); out("settings=" + JsonUtil.serialize(settings)); startTime = new Date(); final Timer timer = new Timer(); final int threadCount = settings.threads; final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( threadCount, threadCount, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(threadCount + 1) ); timer.scheduleAtFixedRate(new DebugOutputTimerTask(),5 * 1000, 30 * 1000); for (int loopCount = 0; loopCount < threadCount; loopCount++) { threadPoolExecutor.execute(new PopulatorThread()); } threadPoolExecutor.shutdown(); threadPoolExecutor.awaitTermination(1, TimeUnit.DAYS); timer.cancel(); out("bulk operations completed"); out("settings=" + JsonUtil.serialize(settings)); out(" results=" + JsonUtil.serialize(makeResults())); outputDebugInfo(); } private class PopulatorThread extends Thread { public void run() { final RandomValueMaker randomValueMaker = new RandomValueMaker(settings.valueLength); while (TimeDuration.fromCurrent(startTime).isShorterThan(settings.testDuration)) { final Collection<PwmLogEvent> events = makeEvents(randomValueMaker); for (final PwmLogEvent logEvent : events) { localDBLogger.writeEvent(logEvent); eventRateMeter.markEvents(1); eventsAdded.incrementAndGet(); } } } } private Collection<PwmLogEvent> makeEvents(final RandomValueMaker randomValueMaker) { final int count = settings.batchSize; final Collection<PwmLogEvent> events = new ArrayList<>(); for (int i = 0; i < count; i++) { final String description = randomValueMaker.next(); PwmLogEvent event = PwmLogEvent.createPwmLogEvent( Instant.now(), LocalDBLogger.class.getName(), description, "", "", null, null, PwmLogLevel.TRACE); events.add(event); } return events; } private void outputDebugInfo() { final StringBuilder sb = new StringBuilder(); sb.append("added ").append(numberFormat.format(eventsAdded.get())); sb.append(", size: ").append(StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation()))); sb.append(", eventsInDb: ").append(figureEventsInDbStat()); sb.append(", free: ").append(StringUtil.formatDiskSize( FileSystemUtility.diskSpaceRemaining(localDB.getFileLocation()))); sb.append(", eps: ").append(eventRateMeter.readEventRate().setScale(0, RoundingMode.UP)); sb.append(", remain: ").append(settings.testDuration.subtract(TimeDuration.fromCurrent(startTime)).asCompactString()); sb.append(", tail: ").append(TimeDuration.fromCurrent(localDBLogger.getTailDate()).asCompactString()); out(sb.toString()); } private String figureEventsInDbStat() { final long maxEvents = config.readSettingAsLong(PwmSetting.EVENTS_PWMDB_MAX_EVENTS); final long eventCount = localDBLogger.getStoredEventCount(); final Percent percent = new Percent(eventCount,maxEvents); return numberFormat.format(localDBLogger.getStoredEventCount()) + "/" + numberFormat.format(maxEvents) + " (" + percent.pretty(2) + ")"; } private Results makeResults() { Results results = new Results(); results.dbClass = config.readAppProperty(AppProperty.LOCALDB_IMPLEMENTATION); results.duration = TimeDuration.fromCurrent(startTime).asCompactString(); results.recordsAdded = eventsAdded.get(); results.dbSize = StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation())); results.eventsInDb = figureEventsInDbStat(); return results; } private class DebugOutputTimerTask extends TimerTask { public void run() { outputDebugInfo(); } } private static class Settings implements Serializable { private TimeDuration testDuration; private int threads; private int valueLength; private int batchSize; } private static class Results implements Serializable { private String dbClass; private String duration; private int recordsAdded; private String dbSize; private String eventsInDb; } private static class RandomValueMaker { private int outputLength; final StringBuffer randomValue = new StringBuffer(); final Random random = new Random(); public RandomValueMaker(final int outputLength) { this.outputLength = outputLength; randomValue.append(PwmRandom.getInstance().alphaNumericString(outputLength * 50)); } public String next() { final int randomPos = random.nextInt(randomValue.length() - 1); randomValue.replace(randomPos, randomPos + 1,String.valueOf(random.nextInt(9))); final int startPos = random.nextInt(randomValue.length() - outputLength); final int endPos = startPos + outputLength; return randomValue.substring(startPos,endPos); } } }