package org.mongodb.morphia.issue148; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import org.bson.types.ObjectId; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.mongodb.morphia.TestBase; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; import org.mongodb.morphia.logging.Logger; import org.mongodb.morphia.logging.MorphiaLoggerFactory; import org.mongodb.morphia.mapping.cache.DefaultEntityCache; import org.mongodb.morphia.mapping.cache.EntityCache; import org.mongodb.morphia.query.FindOptions; import org.mongodb.morphia.query.Query; import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import static java.lang.String.format; @Ignore("enable when testing performance issues") public class TestAsListPerf extends TestBase { private static final Logger LOG = MorphiaLoggerFactory.get(TestAsListPerf.class); private final int nbOfAddresses = 500; private final int nbOfTasks = 200; private final int threadPool = 10; @Test public void compareDriverAndMorphiaQueryingMultithreaded() throws InterruptedException { final Result mongoQueryThreadsResult = new Result(nbOfTasks); final List<MongoQueryThread> mongoThreads = new ArrayList<MongoQueryThread>(nbOfTasks); for (int i = 0; i < nbOfTasks; i++) { mongoThreads.add(new MongoQueryThread(mongoQueryThreadsResult, nbOfAddresses)); } final ExecutorService mongoPool = Executors.newFixedThreadPool(threadPool); for (final MongoQueryThread mongoQueryThread : mongoThreads) { mongoPool.execute(mongoQueryThread); } mongoPool.shutdown(); mongoPool.awaitTermination(30, TimeUnit.SECONDS); final Result morphiaQueryThreadsResult = new Result(nbOfTasks); final List<MorphiaQueryThread> morphiaThreads = new ArrayList<MorphiaQueryThread>(nbOfTasks); for (int i = 0; i < nbOfTasks; i++) { morphiaThreads.add(new MorphiaQueryThread(morphiaQueryThreadsResult, nbOfAddresses)); } final ExecutorService morphiaPool = Executors.newFixedThreadPool(threadPool); for (final MorphiaQueryThread thread : morphiaThreads) { morphiaPool.execute(thread); } morphiaPool.shutdown(); morphiaPool.awaitTermination(30, TimeUnit.SECONDS); LOG.debug(format("compareDriverAndMorphiaQueryingMultithreaded (%d queries each) - driver: %4.2f ms/pojo (avg), " + "morphia %4.2f ms/pojo (avg)", mongoQueryThreadsResult.results.size(), mongoQueryThreadsResult.getAverageTime(), morphiaQueryThreadsResult.getAverageTime())); } @Test public void compareDriverAndMorphiaQueryingOnce() throws Exception { final double driverAvg = driverQueryAndMorphiaConverter(nbOfAddresses); final double morphiaAvg = morphiaQueryAndMorphiaConverter(nbOfAddresses); LOG.debug(format("compareDriverAndMorphiaQueryingOnce - driver: %4.2f ms/pojo , morphia: %4.2f ms/pojo ", driverAvg, morphiaAvg)); Assert.assertNotNull(driverAvg); } @Test public void compareMorphiaAndDriverQueryingMultithreaded() throws InterruptedException { final Result morphiaQueryThreadsResult = new Result(nbOfTasks); final List<MorphiaQueryThread> morphiaThreads = new ArrayList<MorphiaQueryThread>(nbOfTasks); for (int i = 0; i < nbOfTasks; i++) { morphiaThreads.add(new MorphiaQueryThread(morphiaQueryThreadsResult, nbOfAddresses)); } final ExecutorService morphiaPool = Executors.newFixedThreadPool(threadPool); for (final MorphiaQueryThread thread : morphiaThreads) { morphiaPool.execute(thread); } morphiaPool.shutdown(); morphiaPool.awaitTermination(30, TimeUnit.SECONDS); final Result mongoQueryThreadsResult = new Result(nbOfTasks); final List<MongoQueryThread> mongoThreads = new ArrayList<MongoQueryThread>(nbOfTasks); for (int i = 0; i < nbOfTasks; i++) { mongoThreads.add(new MongoQueryThread(mongoQueryThreadsResult, nbOfAddresses)); } final ExecutorService mongoPool = Executors.newFixedThreadPool(threadPool); for (final MongoQueryThread mongoQueryThread : mongoThreads) { mongoPool.execute(mongoQueryThread); } mongoPool.shutdown(); mongoPool.awaitTermination(30, TimeUnit.SECONDS); LOG.debug(format("compareMorphiaAndDriverQueryingMultithreaded (%d queries each) - driver: %4.2f ms/pojo (avg), " + "morphia: %4.2f ms/pojo (avg)", mongoQueryThreadsResult.results.size(), mongoQueryThreadsResult.getAverageTime(), morphiaQueryThreadsResult.getAverageTime())); } public double driverQueryAndMorphiaConverter(final int nbOfHits) { final long start = System.nanoTime(); final List<DBObject> list = getDs().getDB().getCollection("Address") .find() .sort(new BasicDBObject("name", 1)) .toArray(); final EntityCache entityCache = new DefaultEntityCache(); final List<Address> resultList = new LinkedList<Address>(); for (final DBObject dbObject : list) { final Address address = getMorphia().fromDBObject(getDs(), Address.class, dbObject, entityCache); resultList.add(address); } final long duration = (System.nanoTime() - start) / 1000000; //ns -> ms Assert.assertEquals(nbOfHits, resultList.size()); return (double) duration / nbOfHits; } @Test public void driverQueryingMultithreaded() throws InterruptedException { final Result mongoQueryThreadsResult = new Result(nbOfTasks); final List<MongoQueryThread> mongoThreads = new ArrayList<MongoQueryThread>(nbOfTasks); for (int i = 0; i < nbOfTasks; i++) { mongoThreads.add(new MongoQueryThread(mongoQueryThreadsResult, nbOfAddresses)); } final ExecutorService mongoPool = Executors.newFixedThreadPool(threadPool); for (final MongoQueryThread mongoQueryThread : mongoThreads) { mongoPool.execute(mongoQueryThread); } mongoPool.shutdown(); mongoPool.awaitTermination(30, TimeUnit.SECONDS); LOG.debug(format("driverQueryingMultithreaded - (%d queries) driver: %4.2f ms/pojo", mongoQueryThreadsResult.results.size(), mongoQueryThreadsResult.getAverageTime())); } public double morphiaQueryAndMorphiaConverter(final int nbOfHits) { final Query<Address> query = getDs().find(Address.class). order("name"); final long start = System.nanoTime(); final List<Address> resultList = query.asList(); final long duration = (System.nanoTime() - start) / 1000000; //ns -> ms Assert.assertEquals(nbOfHits, resultList.size()); return (double) duration / nbOfHits; } @Test public void morphiaQueryingMultithreaded() throws InterruptedException { final Result morphiaQueryThreadsResult = new Result(nbOfTasks); final List<MorphiaQueryThread> morphiaThreads = new ArrayList<MorphiaQueryThread>(nbOfTasks); for (int i = 0; i < nbOfTasks; i++) { morphiaThreads.add(new MorphiaQueryThread(morphiaQueryThreadsResult, nbOfAddresses)); } final ExecutorService morphiaPool = Executors.newFixedThreadPool(threadPool); for (final MorphiaQueryThread thread : morphiaThreads) { morphiaPool.execute(thread); } morphiaPool.shutdown(); morphiaPool.awaitTermination(30, TimeUnit.SECONDS); LOG.debug(format("morphiaQueryingMultithreaded - (%d queries) morphia: %4.2f ms/pojo", morphiaQueryThreadsResult.results.size(), morphiaQueryThreadsResult.getAverageTime())); } @Override public void setUp() { super.setUp(); getMorphia().map(Address.class); if (getDs().getCount(Address.class) == 0) { for (int i = 0; i < nbOfAddresses; i++) { final Address address = new Address(i); getDs().save(address); } getDs().find(Address.class).filter("name", "random") .fetch(new FindOptions() .limit(-1)); } } @Override public void cleanup() { //do nothing... } static class Result { private final Vector<Double> results; public Result(final int nbOfHits) { results = new Vector<Double>(nbOfHits); } public double getAverageTime() { Double total = 0d; for (final Double duration : results) { total += duration; } return total / results.size(); } } @Entity private static class Address { @Id private ObjectId id; private int parity; private String name = "Scott"; private String street = "3400 Maple"; private String city = "Manhattan Beach"; private String state = "CA"; private int zip = 94114; private Date added = new Date(); public Address() { } public Address(final int i) { parity = i % 2 == 0 ? 1 : 0; name += i; street += i; city += i; state += i; zip += i; } } class MorphiaQueryThread implements Runnable { private final Result result; private final int nbOfHits; public MorphiaQueryThread(final Result result, final int nbOfHits) { this.result = result; this.nbOfHits = nbOfHits; } @Override public void run() { result.results.add(morphiaQueryAndMorphiaConverter(nbOfHits)); } } class MongoQueryThread implements Runnable { private final Result result; private final int nbOfHits; public MongoQueryThread(final Result result, final int nbOfHits) { this.result = result; this.nbOfHits = nbOfHits; } @Override public void run() { result.results.add(driverQueryAndMorphiaConverter(nbOfHits)); } } }