/* * PerformanceBenchmark.java * * Created on August 10, 2007, 12:32 PM * * Description: Provides a performance test for the RDF Entity Manager. * * Copyright (C) August 10, 2007 Stephen L. Reed. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.texai.kb.persistence.benchmark; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Calendar; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import net.jcip.annotations.Immutable; import net.jcip.annotations.NotThreadSafe; import net.sf.ehcache.CacheManager; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.openrdf.OpenRDFException; import org.openrdf.model.URI; import org.openrdf.query.MalformedQueryException; import org.openrdf.query.QueryLanguage; import org.openrdf.query.TupleQuery; import org.openrdf.query.TupleQueryResult; import org.openrdf.repository.RepositoryConnection; import org.openrdf.repository.RepositoryException; import org.texai.kb.CacheInitializer; import org.texai.kb.Constants; import org.texai.kb.persistence.DistributedRepositoryManager; import org.texai.kb.persistence.RDFEntityManager; import org.texai.kb.persistence.RDFEntityPersister; import org.texai.kb.persistence.RDFUtility; import org.texai.util.TexaiException; /** Provides a performance benchmark for measuring the rate at which test RDF entities can be loaded from a Sesame RDF store. * * @author reed */ @NotThreadSafe public final class PerformanceBenchmark { /** the number of reading threads */ private static final int NBR_THREADS = Runtime.getRuntime().availableProcessors(); //private static final int NBR_THREADS = 1; /** the indicator whether to clear and re-populate the repository */ /** the logger */ private static final Logger LOGGER = Logger.getLogger(PerformanceBenchmark.class); /** the RDF entity manager */ private RDFEntityManager rdfEntityManager = null; /** the number of linked RDF test entities to create */ private int nbrRDFTestEntitiesToCreate = 20000; /** the number of linked RDF test entities to randomly read */ private int nbrRDFTestEntitiesToRead = 40000; /** the array of RDF test entity identifying URIs */ private URI[] rdfTestEntityURIs = new URI[nbrRDFTestEntitiesToCreate]; /** the executor */ private final ExecutorService executor; /** Creates a new instance of PerformanceBenchmark. */ public PerformanceBenchmark() { executor = Executors.newFixedThreadPool(NBR_THREADS); } /** Initializes the application. */ public void initialization() { CacheInitializer.initializeCaches(); getClass().getClassLoader().setDefaultAssertionStatus(true); DistributedRepositoryManager.clearNamedRepository("Benchmark"); Logger.getLogger(RDFEntityPersister.class).setLevel(Level.OFF); Logger.getLogger(RDFUtility.class).setLevel(Level.OFF); rdfEntityManager = new RDFEntityManager(); } /** Finalizes the application. */ public void finalization() { CacheManager.getInstance().shutdown(); executor.shutdown(); LOGGER.info("closing the RDF entity manager"); rdfEntityManager.close(); LOGGER.info("shutting down the Sesame2 repositories"); DistributedRepositoryManager.shutDown(); LOGGER.info("PerformanceBenchmark completed"); } /** Creates linked RDF test entities. */ public void createLinkedRDFTestEntities() { LOGGER.info("creating " + nbrRDFTestEntitiesToCreate + " linked RDFTestEntity instances"); final int nbrOfLinkedRDFTestEntityPairs = nbrRDFTestEntitiesToCreate / 2; final long startMillis = System.currentTimeMillis(); for (int i = 1; i <= nbrOfLinkedRDFTestEntityPairs; i++) { createRDFTestEntity(i); } long secondsDuration = (System.currentTimeMillis() - startMillis) / 1000; if (secondsDuration == 0) { secondsDuration = 1; } LOGGER.info("created " + nbrRDFTestEntitiesToCreate + " at the rate of " + nbrRDFTestEntitiesToCreate / secondsDuration + " per second"); } /** Queries the instance URIs. */ public void queryInstanceURIs() { LOGGER.info("querying the instance URIs"); try { final RepositoryConnection queryRepositoryConnection = DistributedRepositoryManager.getInstance().getRepositoryConnectionForRepositoryName("Benchmark"); final String queryString = "SELECT s FROM {s} rdf:type {<http://texai.org/texai/org.texai.kb.persistence.benchmark.RDFTestEntity>}"; LOGGER.info("query " + queryString); final TupleQuery subjectsTupleQuery = queryRepositoryConnection.prepareTupleQuery(QueryLanguage.SERQL, queryString); final List<URI> instanceURIs = new ArrayList<>(); final TupleQueryResult tupleQueryResult = subjectsTupleQuery.evaluate(); while (tupleQueryResult.hasNext()) { instanceURIs.add((URI) tupleQueryResult.next().getBinding("s").getValue()); } tupleQueryResult.close(); LOGGER.info("closing the query repository connection"); queryRepositoryConnection.close(); if (instanceURIs.isEmpty()) { throw new TexaiException("no test entities selected"); } rdfTestEntityURIs = new URI[instanceURIs.size()]; rdfTestEntityURIs = instanceURIs.toArray(rdfTestEntityURIs); } catch (final MalformedQueryException ex) { throw new TexaiException(ex); } catch (final RepositoryException ex) { throw new TexaiException(ex); } catch (final OpenRDFException ex) { throw new TexaiException(ex); } LOGGER.info("Found " + rdfTestEntityURIs.length + " instance URIs"); } /** Randomly reads linked RDF test entities. */ public void readLinkedRDFTestEntities() { LOGGER.info("randomly reading " + nbrRDFTestEntitiesToRead); final long startMillis = System.currentTimeMillis(); final CountDownLatch doneSignal = new CountDownLatch(NBR_THREADS); for (int i = 0; i < NBR_THREADS; i++) { executor.execute(new LinkedRDFTestEntityReaderRunnable(doneSignal, i + 1)); } try { doneSignal.await(); } catch (InterruptedException ex) { throw new TexaiException(ex); } double secondsDuration = (float) ((System.currentTimeMillis() - startMillis)) / 1000.0d; if (secondsDuration == 0) { secondsDuration = 1; } LOGGER.info("read " + nbrRDFTestEntitiesToRead + " at the rate of " + nbrRDFTestEntitiesToRead / secondsDuration + " per second"); } /** Gets the number of linked RDF test entities to create. * * @return the nbrRDFTestEntitiesToCreate */ public int getNbrRDFTestEntitiesToCreate() { return nbrRDFTestEntitiesToCreate; } /** Sets the number of linked RDF test entities to create. * * @param nbrRDFTestEntitiesToCreate the nbrRDFTestEntitiesToCreate to set */ public void setNbrRDFTestEntitiesToCreate(final int nbrRDFTestEntitiesToCreate) { this.nbrRDFTestEntitiesToCreate = nbrRDFTestEntitiesToCreate; } /** Gets the number of linked RDF test entities to randomly read. * * @return the nbrRDFTestEntitiesToRead */ public int getNbrRDFTestEntitiesToRead() { return nbrRDFTestEntitiesToRead; } /** Sets the number of linked RDF test entities to randomly read. * * @param nbrRDFTestEntitiesToRead the nbrRDFTestEntitiesToRead to set */ public void setNbrRDFTestEntitiesToRead(final int nbrRDFTestEntitiesToRead) { this.nbrRDFTestEntitiesToRead = nbrRDFTestEntitiesToRead; } /** A parallel runnable that loads random entity URIs. */ @Immutable class LinkedRDFTestEntityReaderRunnable implements Runnable { /** the count down latch that synchronizes the calling thread */ private final CountDownLatch doneSignal; /** the thread id */ private final int threadID; /** Constructs a new LinkedRDFTestEntityReaderRunnable instance. * * @param doneSignal the count down latch that synchronizes the calling thread * @param threadID the identification for this runnable */ public LinkedRDFTestEntityReaderRunnable(final CountDownLatch doneSignal, final int threadID) { //Preconditions assert doneSignal != null : "doneSignal must not be null"; this.doneSignal = doneSignal; this.threadID = threadID; } /** Executes this thread. */ @Override public void run() { final Random random = new Random(); final RDFEntityManager threadRDFEntityManager = new RDFEntityManager(); LOGGER.info("starting " + threadID); Thread.currentThread().setName("reader " + threadID); int nbrTestEntitiesRead = 0; for (int i = 1; i < (getNbrRDFTestEntitiesToRead() / NBR_THREADS); i++) { nbrTestEntitiesRead++; final URI randomURI = rdfTestEntityURIs[random.nextInt(rdfTestEntityURIs.length - 1)]; final RDFTestEntity randomRDFTestEntity = threadRDFEntityManager.find( RDFTestEntity.class, randomURI); verifyRDFTestEntity(randomRDFTestEntity); if (i % 10000 == 0) { LOGGER.info(i + " --> " + randomRDFTestEntity.getName() + " thread " + threadID); LOGGER.info(" cache" + CacheManager.getInstance().getCache(Constants.CACHE_CONNECTED_RDF_ENTITY_URIS).getStatistics().toString()); CacheInitializer.resetCaches(); } } LOGGER.info("thread " + threadID + " read " + nbrTestEntitiesRead + " entities"); threadRDFEntityManager.close(); doneSignal.countDown(); } } /** Creates two linked RDF test entities with the given serial number suffix. * * @param serialNbr the given serial number suffix */ private void createRDFTestEntity(final int serialNbr) { //preconditions assert serialNbr > 0 : "serialNbr must be positive"; final RDFTestEntity rdfTestEntity1 = new RDFTestEntity(); final RDFTestEntity rdfTestEntity2 = new RDFTestEntity(); rdfTestEntity1.setDontCareField("do not care"); rdfTestEntity1.setFavoriteTestRDFEntityPeer(rdfTestEntity2); rdfTestEntity1.setMaxNbrOfScooterRiders(2); List<RDFTestEntity> myPeers = new ArrayList<>(1); myPeers.add(rdfTestEntity2); rdfTestEntity1.setMyPeers(myPeers); rdfTestEntity1.setName("TestDomainEntity " + serialNbr); rdfTestEntity1.setNumberOfCrew(1); final String[] comments1 = {"comment 1", "comment 2"}; rdfTestEntity1.setComment(comments1); final Set<String> cyclistNotes = new HashSet<>(); cyclistNotes.add("note 1"); cyclistNotes.add("note 2"); rdfTestEntity2.setDontCareField("do not care"); rdfTestEntity2.setFavoriteTestRDFEntityPeer(rdfTestEntity2); rdfTestEntity2.setMaxNbrOfScooterRiders(2); myPeers = new ArrayList<>(1); myPeers.add(rdfTestEntity1); rdfTestEntity2.setMyPeers(myPeers); final List<Double> myPeersStrengths = new ArrayList<>(); myPeersStrengths.add(Double.valueOf(0.5d)); rdfTestEntity2.setName("LinkedTestDomainEntity " + serialNbr); rdfTestEntity2.setNumberOfCrew(1); final String[] comments2 = {"comment 1", "comment 2"}; rdfTestEntity2.setComment(comments2); rdfTestEntity1.setUuidField(UUID.randomUUID()); rdfTestEntity2.setUuidField(UUID.randomUUID()); // set XML datatype fields in the first test RDF entity rdfTestEntity1.setByteField((byte) 5); rdfTestEntity1.setIntField(6); rdfTestEntity1.setLongField(7L); rdfTestEntity1.setFloatField(1.1F); rdfTestEntity1.setDoubleField(1.2D); rdfTestEntity1.setBigIntegerField(new BigInteger("100")); rdfTestEntity1.setBigDecimalField(new BigDecimal("100.001")); rdfTestEntity1.setCalendarField(Calendar.getInstance()); rdfTestEntity1.setDateField(Calendar.getInstance().getTime()); // set XML datatype fields in the linked test RDF entity rdfTestEntity2.setByteField((byte) 5); rdfTestEntity2.setIntField(6); rdfTestEntity2.setLongField(7L); rdfTestEntity2.setFloatField(1.1F); rdfTestEntity2.setDoubleField(1.2D); rdfTestEntity2.setBigIntegerField(new BigInteger("100")); rdfTestEntity2.setBigDecimalField(new BigDecimal("100.001")); rdfTestEntity2.setCalendarField(Calendar.getInstance()); rdfTestEntity2.setDateField(Calendar.getInstance().getTime()); // persist will cascade to rdfTestEntity2 rdfEntityManager.persist(rdfTestEntity1); rdfTestEntityURIs[serialNbr - 1] = rdfTestEntity1.getId(); } /** Verifies that the given RDF test entity's fields have been correctly loaded from the RDF store. * * @param rdfTestEntity the given RDF test entity */ private void verifyRDFTestEntity(final RDFTestEntity rdfTestEntity) { assert rdfTestEntity != null : "rdfTestEntity must not be null"; assert rdfTestEntity.getDontCareField() == null; assert rdfTestEntity.getFavoriteTestRDFEntityPeer() != null; assert rdfTestEntity.getMaxNbrOfScooterRiders() == 2; assert rdfTestEntity.getMyPeers().size() == 1; assert rdfTestEntity.getName().indexOf("TestDomainEntity") > -1 : " name: '" + rdfTestEntity.getName() + "'"; assert rdfTestEntity.getNumberOfCrew() == 1; assert rdfTestEntity.getComment().length == 2; assert rdfTestEntity.getByteField() == (byte) 5; assert rdfTestEntity.getIntField() == 6; assert rdfTestEntity.getLongField() == 7L; assert rdfTestEntity.getFloatField() > 1.0F; assert rdfTestEntity.getDoubleField() > 1.1D; assert rdfTestEntity.getBigIntegerField().equals(new BigInteger("100")); assert rdfTestEntity.getBigDecimalField().equals(new BigDecimal("100.001")); assert rdfTestEntity.getCalendarField() != null; assert rdfTestEntity.getDateField() != null; assert rdfTestEntity.getUuidField() != null; } /** Executes this application. * * @param args the command line arguments (unused) */ public static void main(final String[] args) { final PerformanceBenchmark performanceBenchmark = new PerformanceBenchmark(); performanceBenchmark.initialization(); performanceBenchmark.createLinkedRDFTestEntities(); performanceBenchmark.queryInstanceURIs(); performanceBenchmark.readLinkedRDFTestEntities(); performanceBenchmark.finalization(); } }