package uk.ac.ebi.fg.myequivalents.test.scaling;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import org.apache.commons.io.FileUtils;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.ebi.fg.myequivalents.access_control.model.User;
import uk.ac.ebi.fg.myequivalents.access_control.model.User.Role;
import uk.ac.ebi.fg.myequivalents.dao.access_control.UserDao;
import uk.ac.ebi.fg.myequivalents.managers.impl.db.DbManagerFactory;
import uk.ac.ebi.fg.myequivalents.managers.interfaces.EntityMappingManager;
import uk.ac.ebi.fg.myequivalents.managers.interfaces.EntityMappingSearchResult;
import uk.ac.ebi.fg.myequivalents.managers.interfaces.ManagerFactory;
import uk.ac.ebi.fg.myequivalents.managers.interfaces.ServiceManager;
import uk.ac.ebi.fg.myequivalents.model.Entity;
import uk.ac.ebi.fg.myequivalents.model.Service;
import uk.ac.ebi.fg.myequivalents.provenance.interfaces.ProvManagerFactory;
import uk.ac.ebi.fg.myequivalents.provenance.interfaces.ProvRegistryManager;
import uk.ac.ebi.fg.myequivalents.provenance.model.ProvenanceRegisterEntry;
import uk.ac.ebi.fg.myequivalents.resources.Resources;
import uk.ac.ebi.fg.myequivalents.utils.EntityIdResolver;
import uk.ac.ebi.utils.time.XStopWatch;
import com.google.code.tempusfugit.concurrency.ConcurrentRule;
import com.google.code.tempusfugit.concurrency.annotations.Concurrent;
/**
* Perform some scaling tests.
*
* <dl><dt>date</dt><dd>Mar 1, 2013</dd></dl>
* @author Marco Brandizi
*
*/
public class RandomMappingsTest
{
// An editor is needed for writing operations.
public static final String editorPass = "test.password";
public static final String editorSecret = "test.secret";
public static User editorUser = new User (
"test.editor", "Test Editor", "User", editorPass, "test editor notes", Role.EDITOR, editorSecret );
/**
* How many parallel threads are instantiated that run methods like {@link #readRandomMappings()}.
* Beware that {@link #generateRandomMappings()} will called before each reading thread
* (hence {@link #NBUNDLES} * {@link #NREADING_THREADS} bundles will be generated).
*/
public static final int NREADING_THREADS = 1;
/**
* How many random readings per thread are done by {@link #readRandomMappings()}.
*
*/
public static final int NREADINGS = 5000;
/** No of services that are generated for the test */
public static final int NSERVICES = 20;
/** No of types generated for the test */
public static final int NTYPES = 5;
/** No of random bundles generated for the test
* This is overridden by the system property "myequivalents.test.scaling.nbundles"
*/
public static final int NBUNDLES = (int) 10000/NREADING_THREADS;
/** Bundles have a random size between 2 and this value */
public static final int MAX_BUNDLE_SIZE = 5;
/** Sometimes pair of entities mappings are stored as mappings, in addition to storing a whole bundle. this is
* done to cover the performance of all operations. This is the ratio (range is 0-100) with which this operation
* is done.
*/
public static final int SINGLE_MAPPING_RATIO = 0;
public static final int URI_READING_RATIO = 50;
/**
* A number of readings in methods like {@link #readRandomMappings()} are about non-existing mappings
*/
public static final int VOID_READING_RATIO = 50;
/**
* Random entities are generated and the IDs put here.
*/
public static final String TEST_ENTITIES_FILE_PATH = "target/random_generated_entity_ids.lst";
@Rule
public ConcurrentRule concurrentRule = new ConcurrentRule ();
private int executionIdx = -1;
private Service[] services;
private Logger log = LoggerFactory.getLogger ( Resources.class );
/**
* Creates a bunch of services, numbered from 1 to {@link #NSERVICES}.
* This is only called when {@link #NREADING_THREADS} is 1. Cannot work in multi-thread mode. TODO: fix.
*/
private synchronized void initRandomGeneration () throws Exception
{
// Do it only once in case of multi-threading
if ( ++this.executionIdx > 0 ) return;
ManagerFactory mgrFact = (ManagerFactory) Resources.getInstance ().getMyEqManagerFactory ();
if ( mgrFact instanceof DbManagerFactory )
{
// Sorry I can only clean-up stuff if I'm directly connected to the DB
//
EntityManager em = ((DbManagerFactory) mgrFact).getEntityManagerFactory ().createEntityManager ();
// Removes existing test data
EntityTransaction ts = em.getTransaction ();
ts.begin ();
em.createNativeQuery ( "delete from ENTITY_MAPPING where SERVICE_NAME LIKE '%scaling%'" ).executeUpdate ();
em.createNativeQuery ( "delete from service where NAME LIKE '%scaling%'" ).executeUpdate ();
em.createNativeQuery ( "delete from repository where NAME LIKE '%scaling%'" ).executeUpdate ();
em.createNativeQuery ( "delete from service_collection where NAME LIKE '%scaling%'" ).executeUpdate ();
ts.commit ();
// Store an editor user
//
UserDao userDao = new UserDao ( em );
ts.begin ();
userDao.storeUnauthorized ( editorUser );
ts.commit ();
em.close ();
} // if mgrFact
ServiceManager serviceMgr = Resources.getInstance ().getMyEqManagerFactory ().newServiceManager (
editorUser.getEmail (), editorSecret
);
services = new Service [ NSERVICES ];
for ( int i = 1; i <= NSERVICES; i++ )
{
int typeIdx = ( i % NTYPES + 1 );
Service service = new Service (
"test.scaling.service" + i, "test.scaling.someType" + typeIdx, "A Test Service " + i, "The Description of a Test Service " + i );
service.setUriPattern ( "http://somewhere.in.the.net/test/scaling/service" + i + "/someType" + typeIdx + "/$id" );
services [ i - 1 ] = service;
}
serviceMgr.storeServices ( services );
}
/**
* Generates {@link #NBUNDLES} bundles of sizes ranging from 2 to {@link #MAX_BUNDLE_SIZE}, uses the services
* defined in {@link #init()}.
*
*/
private void generateRandomMappings () throws Exception
{
int execId = 0;
synchronized ( this )
{
initRandomGeneration ();
execId = this.executionIdx;
}
Random rnd = new Random ( System.currentTimeMillis () );
int entIdx = 0;
int nExecutedOperations = 0;
ManagerFactory mgrFact = Resources.getInstance ().getMyEqManagerFactory ();
EntityMappingManager mapMgr = mgrFact.newEntityMappingManager (
editorUser.getEmail (), editorSecret
);
PrintStream out = new PrintStream ( new BufferedOutputStream ( new FileOutputStream ( new File (
TEST_ENTITIES_FILE_PATH + ( execId == 0 ? "" : "." + execId )
) )) );
// This causes the same entities to be stored from different threads, allows you to test trnasaction
// isolation
//execId = 0;
int nbundles = Integer.parseInt ( System.getProperty ( "myequivalents.test.scaling.nbundles", "" + NBUNDLES ) );
// bundle loop
XStopWatch stopw = new XStopWatch ();
for ( int ibundle = 1; ibundle <= nbundles ; ibundle++ )
{
int bundleSize = rnd.nextInt ( MAX_BUNDLE_SIZE - 2 + 1) + 2;
String[] entityIds = new String [ bundleSize ];
// entity loop
for ( int ient = 0; ient < bundleSize; ient++ )
{
int iserv = rnd.nextInt ( NSERVICES ) + 1;
String entityId = "test.scaling.service" + iserv + ":test.scaling.entity_" + execId + "_" + entIdx;
entityIds [ ient ] = entityId;
out.println ( entityId );
// From time to time, use this kind of storage too, just to be sure it's fast enough
boolean mappingStored = ient > 0 && rnd.nextInt ( 100 ) < SINGLE_MAPPING_RATIO;
if ( mappingStored )
{
int prevEntId = entIdx - ( rnd.nextInt ( ient ) + 1 );
String entityPrevId = "test.scaling.service" + iserv + ":test.scaling.entity_" + execId + "_" + prevEntId;
stopw.resumeOrStart ();
mapMgr.storeMappings ( entityPrevId, entityId );
stopw.suspend ();
nExecutedOperations++;
}
log.trace ( "(" + ibundle + ", " + ient + ") done " + (mappingStored ? " (stored immediately)" : "" ) );
entIdx++;
}
stopw.resumeOrStart ();
mapMgr.storeMappingBundle ( entityIds );
stopw.suspend ();
nExecutedOperations++;
String lmsg = ibundle + " bundles stored";
if ( ibundle % 100 == 0 ) log.info ( lmsg ); else log.debug ( lmsg );
} // for ibundle
mapMgr.close ();
if ( mgrFact instanceof DbManagerFactory )
{
// Count how many entities you actually saved
EntityManager em = ((DbManagerFactory) mgrFact).getEntityManagerFactory ().createEntityManager ();
long nents = ((Number) em.createNativeQuery (
"select count(*) from ENTITY_MAPPING where SERVICE_NAME LIKE '%scaling%'" ).getSingleResult ()).longValue ();
em.close ();
log.info ( "-------- Initialisation done, I've stored {} entities in {} secs -------", nents, stopw.getTime () / 1000.0 );
}
else
// Just count the operations, if you can't access datbase records
log.info ( "-------- Initialisation done, I've run {} operations in {} secs -------", nExecutedOperations, stopw.getTime () / 1000.0 );
out.close ();
} // generateRandomMappings()
/**
* Queries for mappings generated by {@link #generateRandomMappings()} and reports about performance.
*/
@Test // @Ignore ( "Not a proper JUnit test, very time-consuming" )
@Concurrent ( count = NREADING_THREADS ) // Cause parallel runs of this test
public void readRandomMappings () throws Exception
{
generateRandomMappings ();
ManagerFactory mgrFact = Resources.getInstance ().getMyEqManagerFactory ();
EntityMappingManager mapMgr = mgrFact.newEntityMappingManager ();
Random rnd = new Random ( System.currentTimeMillis () );
List<String> entityIds = FileUtils.readLines ( new File (
TEST_ENTITIES_FILE_PATH + ( this.executionIdx == 0 ? "" : "." + this.executionIdx
)));
XStopWatch stopw = new XStopWatch ();
for ( int nreading = 1; nreading <= NREADINGS; nreading++ )
{
String entityId = null;
// Do a number of 'doesn't exist' readings
boolean readVoid = rnd.nextInt ( 100 ) < VOID_READING_RATIO;
if ( readVoid )
{
int iserv = rnd.nextInt ( 1000 );
int entIdx = rnd.nextInt ( 1000 );
entityId = "foo.service." + iserv + ":foo.entity." + entIdx;
}
else
{
int entIdx = rnd.nextInt ( entityIds.size () );
entityId = entityIds.get ( entIdx );
// Use URI from time to time
if ( rnd.nextInt ( 100 ) < URI_READING_RATIO ) entityId = toUriSyntax ( entityId );
}
boolean wantRawResult = rnd.nextBoolean ();
stopw.resumeOrStart ();
EntityMappingSearchResult mappings = mapMgr.getMappings ( wantRawResult, entityId );
String payload = mappings.toString (); // simulate a reading operation
stopw.suspend ();
//System.out.println ( "---- Mappings for " + entityId + ":\n" + mappings );
if ( nreading % 100 == 0 ) log.info ( "--- Done {} reads", nreading );
} // for nreading
mapMgr.close ();
if ( mgrFact instanceof DbManagerFactory )
{
// How many mapping entities you worked with?
EntityManager em = ((DbManagerFactory) mgrFact).getEntityManagerFactory ().createEntityManager ();
long nents = ((Number) em.createNativeQuery (
"select count(*) from ENTITY_MAPPING where SERVICE_NAME LIKE '%scaling%'" ).getSingleResult ()).longValue ();
em.close ();
log.info ( String.format ( "-------- Test finished, I've read %d mappings (from %d entities) in %f secs -------",
NREADINGS, nents, stopw.getTime () / 1000.0 ));
}
else
// We have no means to get the no. of DB records, just skip it
log.info ( String.format ( "-------- Test finished, I've read %d mappings in %f secs -------",
NREADINGS, stopw.getTime () / 1000.0 ));
} // readRandomMappings()
/**
* Reads all the mappings in a id list, stored in {@link #TEST_ENTITIES_FILE_PATH}.
*/
@Test @Ignore ( "Not a proper JUnit test, very time-consuming" )
//@Concurrent ( count = NREADING_THREADS ) // Cause parallel runs of this test
public void readAllRandomMappings () throws Exception
{
generateRandomMappings ();
ManagerFactory mgrFact = Resources.getInstance ().getMyEqManagerFactory ();
EntityMappingManager mapMgr = mgrFact.newEntityMappingManager ();
Random rnd = new Random ( System.currentTimeMillis () );
List<String> entityIds = FileUtils.readLines ( new File (
TEST_ENTITIES_FILE_PATH + ( this.executionIdx == 0 ? "" : "." + this.executionIdx )
));
int nreads = entityIds.size ();
XStopWatch stopw = new XStopWatch ();
for ( int ireading = 0; ireading < nreads; ireading++ )
{
String entityId = entityIds.get ( ireading );
// Use URI from time to time
if ( rnd.nextInt ( 100 ) < URI_READING_RATIO ) entityId = toUriSyntax ( entityId );
boolean wantRawResult = rnd.nextBoolean ();
stopw.resumeOrStart ();
EntityMappingSearchResult mappings = mapMgr.getMappings ( wantRawResult, entityId );
String payload = mappings.toString (); // simulate a reading operation
stopw.suspend ();
// System.out.println ( "---- Mappings for " + entityId + ":\n" + mappings );
if ( ireading % 100 == 0 ) log.info ( "--- Done {} reads", ireading );
} // for ireading
mapMgr.close ();
if ( mgrFact instanceof DbManagerFactory )
{
// How many mapping entities you worked with?
EntityManager em = ((DbManagerFactory) mgrFact).getEntityManagerFactory ().createEntityManager ();
long nents = ((Number) em.createNativeQuery (
"select count(*) from ENTITY_MAPPING" ).getSingleResult ()).longValue ();
em.close ();
log.info ( String.format ( "-------- Test finished, I've read %d mappings (from %d entities) in %f secs -------",
nreads, nents, stopw.getTime () / 1000.0 ));
}
// We have no means to get the no. of DB records, just skip it
log.info ( String.format ( "-------- Test finished, I've read %d mappings in %f secs -------",
NREADINGS, stopw.getTime () / 1000.0 ));
} // readAllRandomMappings()
/**
* Queries about the provenance of mappings generated via {@link RandomMappingsTest#generateRandomMappings()} and
* reports about performance.
*
* WARNING: this won't work unless you test with a Maven profile that it's using provenance extenstions.
*
*/
@Test @Ignore ( "Not a proper JUnit test, very time-consuming" )
@Concurrent ( count = NREADING_THREADS ) // Cause parallel runs of this test
public void readRandomMappingProvs () throws Exception
{
generateRandomMappings ();
ProvManagerFactory mgrFact = Resources.getInstance ().getMyEqManagerFactory ();
ProvRegistryManager provMgr = mgrFact.newProvRegistryManager ( editorUser.getEmail(), editorSecret );
EntityMappingManager mapMgr = mgrFact.newEntityMappingManager ( editorUser.getEmail(), editorSecret );
Random rnd = new Random ( System.currentTimeMillis () );
List<String> entityIds = FileUtils.readLines ( new File (
TEST_ENTITIES_FILE_PATH + ( this.executionIdx == 0 ? "" : "." + this.executionIdx )
));
XStopWatch stopw = new XStopWatch ();
for ( int nreading = 1; nreading <= NREADINGS; nreading++ )
{
String entityId1 = null, entityId2 = null;
int entIdx = rnd.nextInt ( entityIds.size () );
entityId1 = entityIds.get ( entIdx );
// Get the second entity among those mapped by this
EntityMappingSearchResult maps = mapMgr.getMappings ( true, entityId1 );
Iterator<EntityMappingSearchResult.Bundle> mbsItr = maps.getBundles ().iterator ();
if ( !mbsItr.hasNext () ) continue;
for ( Entity ment: mbsItr.next ().getEntities () )
if ( !( ment.getServiceName () + ":" + ment.getAccession () ).equals ( entityId1 ) )
{
entityId2 = ment.getServiceName () + ":" + ment.getAccession ();
break;
}
if ( entityId2 == null ) continue;
List<String> validUsers = null;
boolean wantUserFilter = rnd.nextBoolean ();
if ( wantUserFilter )
validUsers = Arrays.asList ( editorUser.getEmail() );
stopw.resumeOrStart ();
Set<List<ProvenanceRegisterEntry>> provs = provMgr.findMappingProv ( entityId1, entityId2, validUsers );
String payload = provs.toString (); // simulate a reading operation
stopw.suspend ();
//log.info ( MessageFormat.format ( "---- Provs for ({0}, {1}):\n{2}", entityId1, entityId2, payLoad ) );
if ( nreading % 100 == 0 ) log.info ( "--- Done {} reads, elapsed time: {}s", nreading, stopw.getTime () / 1000d );
} // for nreading
mapMgr.close ();
provMgr.close ();
if ( mgrFact instanceof DbManagerFactory )
{
// How many mapping entities you worked with?
EntityManager em = ((DbManagerFactory) mgrFact).getEntityManagerFactory ().createEntityManager ();
long nents = ((Number) em.createNativeQuery (
"select count(*) from ENTITY_MAPPING where SERVICE_NAME LIKE '%scaling%'" ).getSingleResult ()).longValue ();
em.close ();
log.info ( String.format ( "-------- Test finished, I've read provenance records from %d mappings (from %d entities) in %f secs -------",
NREADINGS, nents, stopw.getTime () / 1000.0 ));
}
else
// We have no means to get the no. of DB records, just skip it
log.info ( String.format ( "-------- Test finished, I've read provenance records from %d mappings in %f secs -------",
NREADINGS, stopw.getTime () / 1000.0 ));
} // readRandomMappingProvs()
/**
* Turns entityId into "<uri>", splitting entityId and using {@link #services}.
*/
private String toUriSyntax ( String entityId )
{
// "test.scaling.service" + iserv + ":test.scaling.entity_" + execId + "_" + entIdx
String idchunks[] = entityId.split ( ":" );
int iserv = Integer.parseInt ( idchunks [ 0 ].substring ( "test.scaling.service".length () ) ) - 1;
Service service = services [ iserv ];
entityId = EntityIdResolver.buildUriFromAcc ( idchunks [ 1 ], service.getUriPattern () );
return "<" + entityId + ">";
}
}