Package org.helidb.resources.perf

Source Code of org.helidb.resources.perf.PerformanceTestRunner$DataPoint

/* HeliDB -- A simple database for Java, http://www.helidb.org
* Copyright (C) 2008, 2009 Karl Gustafsson
*
* This file is a part of HeliDB.
*
* HeliDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeliDB 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, see <http://www.gnu.org/licenses/>.
*/
package org.helidb.resources.perf;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.entityfs.support.exception.WrappedIOException;
import org.entityfs.support.log.LogAdapterHolder;
import org.entityfs.support.log.StdOutLogAdapter;
import org.helidb.Database;
import org.helidb.lang.Record;
import org.helidb.lang.serializer.IntegerSerializer;
import org.helidb.lang.serializer.LongSerializer;
import org.helidb.lang.serializer.StringSerializer;
import org.helidb.txn.Transaction;
import org.helidb.txn.TransactionalDatabase;

public class PerformanceTestRunner
{
  private static final Random RANDOM = new Random(System.currentTimeMillis());

  public static final int TESTDATA_SIZE = 5000100;

  private static final Charset UTF8 = Charset.forName("UTF-8");

  private static final int LINE_HEIGHT = 16;
  private static final int GRAPH_HEIGHT = 300;
  private static final int TITLE_HEIGHT = 2 * LINE_HEIGHT;
  private static final int GRAPH_WIDTH = 400;
  private static final int TABLE_WIDTH = 150;

  private static int[] s_crs_keys;
  private static long[] s_crs_values;
  private static String[] s_vrs_keys;
  private static String[] s_vrs_values;

  private static List<DataPoint> s_initialInserts = new ArrayList<DataPoint>();
  private static List<DataPoint> s_findRecords = new ArrayList<DataPoint>();
  private static List<DataPoint> s_iterateOverKeys = new ArrayList<DataPoint>();
  private static List<DataPoint> s_iterateOverRecords = new ArrayList<DataPoint>();
  private static List<DataPoint> s_insertAdditionalRecords = new ArrayList<DataPoint>();
  private static List<DataPoint> s_updateAdditionalRecords = new ArrayList<DataPoint>();
  private static List<DataPoint> s_deleteAdditionalRecords = new ArrayList<DataPoint>();
  private static List<DataPoint> s_updateRecords = new ArrayList<DataPoint>();
  private static List<DataPoint> s_deleteRecords = new ArrayList<DataPoint>();
  private static List<DataPoint> s_compact = new ArrayList<DataPoint>();

  private static boolean s_createGraphs = false;
  private static String s_graphFileNamePrefix;
  private static String s_database;
  private static String[] s_databaseBackends;
  private static String s_additionalInfo;
  private static boolean s_loadTestData = false;
  private static File s_testDataFile;

  private static class DataPoint
  {
    private final int m_x;
    private final double m_y;

    private DataPoint(int x, double y)
    {
      m_x = x;
      m_y = y;
    }
  }

  private static class DoubleAndInt
  {
    private final double m_double;
    private final int m_int;

    private DoubleAndInt(double d, int i)
    {
      m_double = d;
      m_int = i;
    }
  }

  private static void saveTestData()
  {
    try
    {
      File f = File.createTempFile("perfTest", ".dat");
      System.out.println("Saving test data to " + f);
      OutputStream os = new BufferedOutputStream(new FileOutputStream(f));
      try
      {
        for (int i = 0; i < TESTDATA_SIZE; i++)
        {
          os.write(IntegerSerializer.INSTANCE.serialize(s_crs_keys[i]));
          os.write(LongSerializer.INSTANCE.serialize(s_crs_values[i]));
          byte[] barr = StringSerializer.INSTANCE.serialize(s_vrs_keys[i]);
          os.write(IntegerSerializer.INSTANCE.serialize(barr.length));
          os.write(barr);
          barr = StringSerializer.INSTANCE.serialize(s_vrs_values[i]);
          os.write(IntegerSerializer.INSTANCE.serialize(barr.length));
          os.write(barr);
        }
      }
      finally
      {
        os.close();
      }
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
  }

  private static void loadTestData(File f) throws IOException
  {
    System.out.print("Loading test data from " + f);
    InputStream is = new BufferedInputStream(new FileInputStream(f));
    try
    {
      for (int i = 0; i < TESTDATA_SIZE; i++)
      {
        s_crs_keys[i] = IntegerSerializer.INSTANCE.readInteger(is);
        s_crs_values[i] = LongSerializer.INSTANCE.readLong(is);
        int arrLen = IntegerSerializer.INSTANCE.readInteger(is);
        s_vrs_keys[i] = StringSerializer.INSTANCE.read(is, arrLen);
        arrLen = IntegerSerializer.INSTANCE.readInteger(is);
        s_vrs_values[i] = StringSerializer.INSTANCE.read(is, arrLen);
      }
    }
    finally
    {
      is.close();
    }
  }

  private static void prepareTestData()
  {
    System.out.print("Preparing test data");
    int i = 0;
    Set<Integer> is = new HashSet<Integer>(TESTDATA_SIZE);
    s_crs_keys = new int[TESTDATA_SIZE];
    while (i < TESTDATA_SIZE)
    {
      int n = RANDOM.nextInt();
      if (!is.contains(n))
      {
        is.add(n);
        s_crs_keys[i++] = n;
      }
    }
    is = null;
    System.out.print(".");

    Set<Long> ls = new HashSet<Long>(TESTDATA_SIZE);
    s_crs_values = new long[TESTDATA_SIZE];
    i = 0;
    while (i < TESTDATA_SIZE)
    {
      long n = RANDOM.nextLong();
      if (!ls.contains(n))
      {
        ls.add(n);
        s_crs_values[i++] = n;
      }
    }
    ls = null;
    System.out.print(".");

    Set<String> ss = new HashSet<String>(TESTDATA_SIZE);
    s_vrs_keys = new String[TESTDATA_SIZE];
    i = 0;
    while (i < TESTDATA_SIZE)
    {
      byte[] barr = new byte[16];
      RANDOM.nextBytes(barr);
      String s = new String(barr);
      if (!ss.contains(s))
      {
        ss.add(s);
        s_vrs_keys[i++] = s;
      }
    }
    System.out.print(".");

    ss = null;

    // The values are the keys offset by 13333 positions
    s_vrs_values = new String[TESTDATA_SIZE];
    System.arraycopy(s_vrs_keys, 13333, s_vrs_values, 0, TESTDATA_SIZE - 13333);
    System.arraycopy(s_vrs_keys, 0, s_vrs_values, TESTDATA_SIZE - 13333, 13333);

    System.out.println(".");
    System.gc();
  }

  private static List<TestConfiguration<?, ?>> createTestConfigurations()
  {
    List<TestConfiguration<?, ?>> res = new ArrayList<TestConfiguration<?, ?>>();

    int[][] sizes = new int[][] { { 500, 0, 5 }, { 500, 10, 5 }, { 500, 50, 5 }, { 200, 100, 5 }, { 100, 500, 10 }, { 100, 1000, 10 }, { 20, 5000, 50 }, { 10, 10000, 50 }, { 1, 50000, 100 }, { 1, 100000, 100 }, { 1, 500000, 100 }, { 1, 1000000, 100 }, { 1, 5000000, 100 } };
    int lowCeil = 8;
    int medCeil = 11;
    int highCeil = sizes.length - 1;

    for (int i = 0; i <= lowCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseHeapConfiguration(sz[0], sz[1], sz[2]));
    }

    // Marks the end of a test configuration suite
    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    for (int i = 0; i <= lowCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new LoggingTransactionalDatabaseHeapConfiguration(sz[0], sz[1], sz[2]));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new LoggingTransactionalDatabaseHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new LoggingTransactionalDatabaseHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    for (int i = 0; i <= lowCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new ShadowCopyTransactionalDatabaseHeapConfiguration(sz[0], sz[1], sz[2]));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new ShadowCopyTransactionalDatabaseHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new ShadowCopyTransactionalDatabaseHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseCRSHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseCRSHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    for (int i = 0; i <= highCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseCRSBPlusTreeConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= highCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseCRSBPlusTreeConfiguration(sz[0], sz[1], sz[2], 10, 4096));
    }

    res.add(null);

    for (int i = 0; i <= highCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseCRSBPlusTreeConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    for (int i = 0; i <= highCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseCRSBPlusTreeConfiguration(sz[0], sz[1], sz[2], 50, 4096));
    }

    res.add(null);

    for (int i = 0; i <= lowCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new SimpleDatabaseCRSHeapConfiguration(sz[0], sz[1], sz[2]));
    }

    res.add(null);

    for (int i = 0; i <= lowCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new LoggingTransactionalDatabaseCRSHeapConfiguration(sz[0], sz[1], sz[2]));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new LoggingTransactionalDatabaseCRSHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new LoggingTransactionalDatabaseCRSHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new LoggingTransactionalDatabaseCRSBPlusTreeConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new LoggingTransactionalDatabaseCRSBPlusTreeConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    for (int i = 0; i <= lowCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new ShadowCopyTransactionalDatabaseCRSHeapConfiguration(sz[0], sz[1], sz[2]));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new ShadowCopyTransactionalDatabaseCRSHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new ShadowCopyTransactionalDatabaseCRSHeapWBPlusTreeIndexConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new ShadowCopyTransactionalDatabaseCRSBPlusTreeConfiguration(sz[0], sz[1], sz[2], 0, 4096));
    }

    res.add(null);

    for (int i = 0; i <= medCeil; i++)
    {
      int[] sz = sizes[i];
      res.add(new ShadowCopyTransactionalDatabaseCRSBPlusTreeConfiguration(sz[0], sz[1], sz[2], 30, 4096));
    }

    res.add(null);

    return res;
  }

  private static double crsInsertRecords(CRSTestConfiguration tc, Collection<? extends Database<Integer, Long>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.println("Inserting " + noOfRecords + " records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<Integer, Long> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = 0; i < noOfRecords; i++)
        {
          db.insert(s_crs_keys[i], s_crs_values[i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per insert)");
    return res;
  }

  private static double crsFindRecords(CRSTestConfiguration tc, Collection<? extends Database<Integer, Long>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.println("Selecting " + noOfRecords + " records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<Integer, Long> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(true);
        }
        for (int i = 0; i < noOfRecords; i++)
        {
          db.get(s_crs_keys[i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per select)");
    return res;
  }

  private static double iterateOverKeys(TestConfiguration<?, ?> tc, Collection<? extends Database<?, ?>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.print("Iterating over " + noOfRecords + " keys in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<?, ?> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(true);
        }
        Iterator<?> itr = db.keyIterator();
        while (itr.hasNext())
        {
          itr.next();
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per itr.hasNext + itr.next)");
    return res;
  }

  private static double iterateOverRecords(TestConfiguration<?, ?> tc, Collection<? extends Database<?, ?>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.print("Iterating over " + noOfRecords + " records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<?, ?> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(true);
        }
        Iterator<? extends Record<?, ?>> itr = db.iterator();
        while (itr.hasNext())
        {
          itr.next();
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per itr.hasNext + itr.next)");
    return res;
  }

  private static double crsInsertAdditionalRecords(CRSTestConfiguration tc, Collection<? extends Database<Integer, Long>> dbs)
  {
    final int noOfBaseRecords = tc.getNoOfBaseRecords();
    final int noOfAdditionalRecords = tc.getNoOfAdditionalRecords();
    System.out.println("Inserting " + noOfAdditionalRecords + " additional records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<Integer, Long> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = noOfBaseRecords; i < noOfBaseRecords + noOfAdditionalRecords; i++)
        {
          db.insert(s_crs_keys[i], s_crs_values[i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfAdditionalRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per insert)");
    return res;
  }

  private static double crsUpdateAdditionalRecords(CRSTestConfiguration tc, Collection<? extends Database<Integer, Long>> dbs)
  {
    final int noOfBaseRecords = tc.getNoOfBaseRecords();
    final int noOfAdditionalRecords = tc.getNoOfAdditionalRecords();
    System.out.println("Updating " + noOfAdditionalRecords + " additional records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    final int crsValuesLen = s_crs_values.length;
    for (Database<Integer, Long> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = noOfBaseRecords; i < noOfBaseRecords + noOfAdditionalRecords; i++)
        {
          db.update(s_crs_keys[i], s_crs_values[crsValuesLen - i - 1]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfAdditionalRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per update)");
    return res;
  }

  private static double crsDeleteAdditionalRecords(CRSTestConfiguration tc, Collection<? extends Database<Integer, Long>> dbs)
  {
    final int noOfBaseRecords = tc.getNoOfBaseRecords();
    final int noOfAdditionalRecords = tc.getNoOfAdditionalRecords();
    System.out.println("Deleting " + noOfAdditionalRecords + " additional records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<Integer, Long> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = noOfBaseRecords; i < noOfBaseRecords + noOfAdditionalRecords; i++)
        {
          db.delete(s_crs_keys[i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfAdditionalRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per delete)");
    return res;
  }

  private static double crsUpdateRecords(CRSTestConfiguration tc, Collection<? extends Database<Integer, Long>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.println("Updating " + noOfRecords + " (all) records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    final int crsValuesLen = s_crs_values.length;
    for (Database<Integer, Long> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = 0; i < noOfRecords; i++)
        {
          db.update(s_crs_keys[i], s_crs_values[crsValuesLen - i - 1]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per update)");
    return res;
  }

  private static double crsDeleteOneThirdOfAllRecords(CRSTestConfiguration tc, Collection<? extends Database<Integer, Long>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.println("Deleting 1/3rd of " + noOfRecords + " (all) records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<Integer, Long> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = 0; i < noOfRecords / 3; i++)
        {
          db.delete(s_crs_keys[3 * i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / ((noOfRecords / 3) * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per delete)");
    return res;
  }

  private static double compact(TestConfiguration<?, ?> tc, Collection<? extends Database<?, ?>> dbs)
  {
    System.out.println("Compacting " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<?, ?> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        db.compact();
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per database)");
    return res;
  }

  private static void addRecord(List<DataPoint> l, int x, double y)
  {
    Double f = Double.valueOf(y);
    if ((!f.isInfinite()) && (!f.isNaN()))
    {
      l.add(new DataPoint(x, y));
    }
  }

  private static void runTestsFor(CRSTestConfiguration tc)
  {
    Collection<? extends Database<Integer, Long>> dbs = tc.getDatabases();
    int nob = tc.getNoOfBaseRecords();
    addRecord(s_initialInserts, nob, crsInsertRecords(tc, dbs));
    addRecord(s_findRecords, nob, crsFindRecords(tc, dbs));
    addRecord(s_iterateOverKeys, nob, iterateOverKeys(tc, dbs));
    addRecord(s_iterateOverRecords, nob, iterateOverRecords(tc, dbs));
    addRecord(s_insertAdditionalRecords, nob, crsInsertAdditionalRecords(tc, dbs));
    addRecord(s_updateAdditionalRecords, nob, crsUpdateAdditionalRecords(tc, dbs));
    addRecord(s_deleteAdditionalRecords, nob, crsDeleteAdditionalRecords(tc, dbs));
    addRecord(s_updateRecords, nob, crsUpdateRecords(tc, dbs));
    addRecord(s_deleteRecords, nob, crsDeleteOneThirdOfAllRecords(tc, dbs));
    addRecord(s_compact, nob, compact(tc, dbs));
  }

  private static double vrsInsertRecords(VRSTestConfiguration tc, Collection<? extends Database<String, String>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.println("Inserting " + noOfRecords + " records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<String, String> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = 0; i < noOfRecords; i++)
        {
          db.insert(s_vrs_keys[i], s_vrs_values[i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per insert)");
    return res;
  }

  private static double vrsFindRecords(VRSTestConfiguration tc, Collection<? extends Database<String, String>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.println("Selecting " + noOfRecords + " records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<String, String> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(true);
        }
        for (int i = 0; i < noOfRecords; i++)
        {
          db.get(s_vrs_keys[i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per select)");
    return res;
  }

  private static double vrsInsertAdditionalRecords(VRSTestConfiguration tc, Collection<? extends Database<String, String>> dbs)
  {
    final int noOfBaseRecords = tc.getNoOfBaseRecords();
    final int noOfAdditionalRecords = tc.getNoOfAdditionalRecords();
    System.out.println("Inserting " + noOfAdditionalRecords + " additional records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<String, String> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = noOfBaseRecords; i < noOfBaseRecords + noOfAdditionalRecords; i++)
        {
          db.insert(s_vrs_keys[i], s_vrs_values[i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfAdditionalRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per insert)");
    return res;
  }

  private static double vrsUpdateAdditionalRecords(VRSTestConfiguration tc, Collection<? extends Database<String, String>> dbs)
  {
    final int noOfBaseRecords = tc.getNoOfBaseRecords();
    final int noOfAdditionalRecords = tc.getNoOfAdditionalRecords();
    System.out.println("Updating " + noOfAdditionalRecords + " additional records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    final int crsValuesLen = s_crs_values.length;
    for (Database<String, String> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = noOfBaseRecords; i < noOfBaseRecords + noOfAdditionalRecords; i++)
        {
          db.update(s_vrs_keys[i], s_vrs_values[crsValuesLen - i - 1]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfAdditionalRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per update)");
    return res;
  }

  private static double vrsDeleteAdditionalRecords(VRSTestConfiguration tc, Collection<? extends Database<String, String>> dbs)
  {
    final int noOfBaseRecords = tc.getNoOfBaseRecords();
    final int noOfAdditionalRecords = tc.getNoOfAdditionalRecords();
    System.out.println("Deleting " + noOfAdditionalRecords + " additional records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<String, String> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = noOfBaseRecords; i < noOfBaseRecords + noOfAdditionalRecords; i++)
        {
          db.delete(s_vrs_keys[i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfAdditionalRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per delete)");
    return res;
  }

  private static double vrsUpdateRecords(VRSTestConfiguration tc, Collection<? extends Database<String, String>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.println("Updating " + noOfRecords + " (all) records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    final int vrsValuesLen = s_vrs_values.length;
    int dbNo = 0;
    int i = 0;
    for (Database<String, String> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (i = 0; i < noOfRecords; i++)
        {
          db.update(s_vrs_keys[i], s_vrs_values[vrsValuesLen - i - 1]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      catch (RuntimeException e)
      {
        System.err.println("DB #" + dbNo);
        System.err.println("Entry " + i);
        throw e;
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
      dbNo++;
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / (noOfRecords * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per update)");
    return res;
  }

  private static double vrsDeleteOneThirdOfAllRecords(VRSTestConfiguration tc, Collection<? extends Database<String, String>> dbs)
  {
    final int noOfRecords = tc.getNoOfBaseRecords();
    System.out.println("Deleting 1/3rd of " + noOfRecords + " (all) records in " + dbs.size() + " databases");
    long start = System.currentTimeMillis();
    for (Database<String, String> db : dbs)
    {
      Transaction txn = null;
      try
      {
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn = Transaction.startTransaction(false);
        }
        for (int i = 0; i < noOfRecords / 3; i++)
        {
          db.delete(s_vrs_keys[3 * i]);
        }
        if (db instanceof TransactionalDatabase<?, ?>)
        {
          txn.commit();
          txn = null;
        }
      }
      finally
      {
        if (txn != null)
        {
          txn.rollback();
        }
      }
    }
    long time = System.currentTimeMillis() - start;
    double res = (float) time / ((noOfRecords / 3) * dbs.size());
    System.out.println("Total time: " + time + " ms. (" + res + " ms per delete)");
    return res;
  }

  private static void runTestsFor(VRSTestConfiguration tc)
  {
    Collection<? extends Database<String, String>> dbs = tc.getDatabases();
    int nob = tc.getNoOfBaseRecords();
    addRecord(s_initialInserts, nob, vrsInsertRecords(tc, dbs));
    addRecord(s_findRecords, nob, vrsFindRecords(tc, dbs));
    addRecord(s_iterateOverKeys, nob, iterateOverKeys(tc, dbs));
    addRecord(s_iterateOverRecords, nob, iterateOverRecords(tc, dbs));
    addRecord(s_insertAdditionalRecords, nob, vrsInsertAdditionalRecords(tc, dbs));
    addRecord(s_updateAdditionalRecords, nob, vrsUpdateAdditionalRecords(tc, dbs));
    addRecord(s_deleteAdditionalRecords, nob, vrsDeleteAdditionalRecords(tc, dbs));
    addRecord(s_updateRecords, nob, vrsUpdateRecords(tc, dbs));
    addRecord(s_deleteRecords, nob, vrsDeleteOneThirdOfAllRecords(tc, dbs));
    addRecord(s_compact, nob, compact(tc, dbs));
  }

  private static String getDataPoints(List<DataPoint> c)
  {
    boolean first = true;
    StringBuilder res = new StringBuilder();
    for (DataPoint dp : c)
    {
      if (!first)
      {
        res.append(',');
      }
      res.append("(x=").append(dp.m_x).append(",y=").append(dp.m_y).append(" ms)");
      first = false;
    }
    return res.toString();
  }

  private static DoubleAndInt calculateYCeiling(double maxValue, int precision)
  {
    double mxLg10 = Math.log10(maxValue);
    int base = (int) Math.ceil(Math.pow(10, precision) * (Math.pow(10, mxLg10 - (int) mxLg10)));
    double ceil = base * Math.pow(10, (int) mxLg10 - precision);
    if ((maxValue / ceil) < 0.75)
    {
      return calculateYCeiling(maxValue, precision + 1);
    }
    else
    {
      int noOfNotches = base;
      while (noOfNotches > 12)
      {
        noOfNotches = noOfNotches / 4;
      }
      if (noOfNotches < 3)
      {
        noOfNotches = noOfNotches * 4;
      }
      return new DoubleAndInt(ceil, noOfNotches);
    }
  }

  /**
   * Calculate the scale of the Y axis.
   * @param data
   * @return
   */
  private static DoubleAndInt calculateYCeiling(List<DataPoint> data)
  {
    double maxValue = 0;
    for (DataPoint dp : data)
    {
      maxValue = Math.max(maxValue, dp.m_y);
    }
    return calculateYCeiling(maxValue, 1);
  }

  private static int calculateXCeiling(List<DataPoint> data)
  {
    int maxValue = 0;
    for (DataPoint dp : data)
    {
      maxValue = Math.max(maxValue, dp.m_x);
    }
    return maxValue;
  }

  private static void drawCrossAt(PrintWriter w, int x, int y)
  {
    w.printf("<line stroke=\"blue\" stroke-width=\"1\" x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\"/>", x - 3, y - 3, x + 3, y + 3);
    w.println();
    w.printf("<line stroke=\"blue\" stroke-width=\"1\" x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\"/>", x - 3, y + 3, x + 3, y - 3);
    w.println();
  }

  private static void createLogarithmicalXAxis(PrintWriter w, int x1, int y1, int x2, int xCeiling)
  {
    double x = xCeiling;
    double xScale = (x2 - x1) / Math.log10(xCeiling);
    int i = 0;
    while (x >= 10)
    {
      int xpos = x1 + (int) Math.round(Math.log10(x) * xScale);
      int textYPos = y1 + 5 + (i++ % 2 == 0 ? 0 : LINE_HEIGHT - 3);
      w.printf("<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" stroke=\"black\" stroke-width=\"1\"/>", xpos, y1 - 3, xpos, y1 + 3);
      w.println();
      w.printf("<text style=\"text-anchor:middle;dominant-baseline:hanging\" x=\"%s\" y=\"%s\">%s</text>", xpos, textYPos, new DecimalFormat("#").format(Math.round(x)));
      w.println();
      x = x / 10;
    }
  }

  private static void createLinearXAxis(PrintWriter w, int x1, int y1, int x2, int xCeiling)
  {
    double pixsPerXNotch = (x2 - x1) / 5;
    for (int i = 0; i <= 5; i++)
    {
      int xpos = x1 + (int) Math.round(i * pixsPerXNotch);
      int textYPos = y1 + 5 + (i % 2 == 0 ? 0 : LINE_HEIGHT - 3);
      w.printf("<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" stroke=\"black\" stroke-width=\"1\"/>", xpos, y1 - 3, xpos, y1 + 3);
      w.println();
      w.printf("<text style=\"text-anchor:middle;dominant-baseline:hanging\" x=\"%s\" y=\"%s\">%s</text>", xpos, textYPos, xCeiling * i / 5);
      w.println();
    }
  }

  private static void createLinearYAxis(PrintWriter w, int x1, int y1, int y2, double yCeiling, int noOfYNotches)
  {
    // Notches on the vertical axis
    double pixsPerYNotch = (y1 - y2) / noOfYNotches;
    for (int i = 1; i <= noOfYNotches; i++)
    {
      int ypos = y1 - (int) Math.round(i * pixsPerYNotch);
      w.printf("<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" stroke=\"black\" stroke-width=\"1\"/>", x1 - 3, ypos, x1 + 3, ypos);
      w.println();
      // String text = new StringBuffer(new
      // DecimalFormat("#.###").format(yCeiling * i /
      // noOfYNotches)).reverse().toString();
      String text = new DecimalFormat("#.###").format(yCeiling * i / noOfYNotches);
      w.printf("<text style=\"text-anchor:end;dominant-baseline:central\" x=\"%s\" y=\"%s\">%s</text>", x1 - 6, ypos, text);
    }
  }

  private static void createLogarithmicalYAxis(PrintWriter w, int x1, int y1, int y2, double yCeiling, double yBase, double yScale)
  {
    double y = yCeiling;
    for (int i = 0; i < 5; i++)
    {
      int ypos = y1 - (int) Math.round(yScale * (Math.log10(y) - yBase));
      w.printf("<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" stroke=\"black\" stroke-width=\"1\"/>", x1 - 3, ypos, x1 + 3, ypos);
      w.println();
      // String text = new StringBuffer(new
      // DecimalFormat("#.###").format(yCeiling * i /
      // noOfYNotches)).reverse().toString();
      String text = new DecimalFormat("#.#####").format(y);
      w.printf("<text style=\"text-anchor:end;dominant-baseline:central\" x=\"%s\" y=\"%s\">%s</text>", x1 - 6, ypos, text);
      y = y / 10;
    }
  }

  private static void createGraph(List<DataPoint> data, String fileName, String title1, String title2, boolean lgX, boolean lgY)
  {
    File f = new File(System.getProperty("java.io.tmpdir") + File.separator + s_graphFileNamePrefix + fileName + ".svg");
    try
    {
      PrintWriter w = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), UTF8)));
      try
      {
        int gw10 = GRAPH_WIDTH / 10;
        int gw20 = GRAPH_WIDTH / 20;
        int gh10 = GRAPH_HEIGHT / 10;
        int gh20 = GRAPH_HEIGHT / 20;
        int x1 = 3 * gw20;
        int x2 = 17 * gw20;
        int y1 = 9 * gh10 + TITLE_HEIGHT;
        int y2 = 3 * gh20 + TITLE_HEIGHT;
        int xCeiling = calculateXCeiling(data);
        double xScale = (x2 - x1) / (lgX ? Math.log10(xCeiling) : (double) xCeiling);
        DoubleAndInt yc = calculateYCeiling(data);
        double yCeiling = yc.m_double;
        int noOfYNotches = Math.max(1, yc.m_int);
        double yBase = lgY ? Math.log10(yCeiling / 100000) : 0;
        double yScale = (y1 - y2) / (lgY ? Math.log10(yCeiling) - yBase : yCeiling);
        w.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
        w.println("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
        w.printf("<svg width=\"%s\" height=\"%s\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">", GRAPH_WIDTH + TABLE_WIDTH, GRAPH_HEIGHT + TITLE_HEIGHT);
        w.println();
        w.println("<defs>");
        // Define the line arrow marker
        w.println("<marker id=\"line-arrow\" viewBox=\"0 0 10 10\" refX=\"1\" refY=\"5\" markerUnits=\"strokeWidth\" orient=\"auto\" markerWidth=\"4\" markerHeight=\"3\">");
        w.println("<polyline points=\"0,0 10,5 0,10 1,5\"/>");
        w.println("</marker>");
        w.println();
        w.println("</defs>");
        // A group setting the default font attributes
        w.println("<g font-family=\"sans-serif\" font-size=\"9pt\">");
        // The title
        w.printf("<text style=\"text-anchor:middle;font-weight:bold\" x=\"%s\" y=\"%s\">%s</text>", (GRAPH_WIDTH + TABLE_WIDTH) / 2, LINE_HEIGHT, title1);
        w.println();
        w.printf("<text style=\"text-anchor:middle;font-weight:bold\" x=\"%s\" y=\"%s\">%s</text>", (GRAPH_WIDTH + TABLE_WIDTH) / 2, 2 * LINE_HEIGHT, title2);
        w.println();
        // The vertical axis
        w.printf("<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" stroke=\"black\" stroke-width=\"2\" marker-end=\"url(#line-arrow)\"/>", x1, y1, x1, y2 - gh10);
        w.println();
        w.printf("<text style=\"text-anchor:end;dominant-baseline:central\" x=\"%s\" y=\"%s\">t (ms)</text>", x1 - 6, y2 - gh10);
        w.println();
        if (lgY)
        {
          createLogarithmicalYAxis(w, x1, y1, y2, yCeiling, yBase, yScale);
        }
        else
        {
          createLinearYAxis(w, x1, y1, y2, yCeiling, noOfYNotches);
        }
        // The horizontal axis
        w.printf("<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" stroke=\"black\" stroke-width=\"2\" marker-end=\"url(#line-arrow)\"/>", x1 - 1, y1, x2 + gw10, y1);
        w.println();
        w.printf("<text style=\"text-anchor:middle;dominant-baseline:hanging\" x=\"%s\" y=\"%s\">x</text>", x2 + gw10 + 2, y1 + 6);
        w.println();
        if (lgX)
        {
          createLogarithmicalXAxis(w, x1, y1, x2, xCeiling);
        }
        else
        {
          createLinearXAxis(w, x1, y1, x2, xCeiling);
        }
        // Crosses over the data points
        StringBuilder dataPointString = new StringBuilder();
        boolean first = true;
        for (DataPoint dp : data)
        {
          if (lgX && (dp.m_x < 10))
          {
            continue;
          }
          int x = x1 + (int) Math.round((lgX ? Math.log10(dp.m_x) : dp.m_x) * xScale);
          int y = y1 - (int) Math.round((lgY ? Math.log10(dp.m_y) - yBase : dp.m_y) * yScale);
          drawCrossAt(w, x, y);

          if (!first)
          {
            dataPointString.append(' ');
          }
          dataPointString.append(x).append(',').append(y);
          first = false;
        }
        // The graph
        w.printf("<polyline points=\"%s\" fill-opacity=\"0\" stroke=\"blue\" stroke-width=\"1\"/>", dataPointString);
        w.println();

        // Table
        int t10 = TABLE_WIDTH / 10;
        int tableStartX = GRAPH_WIDTH;
        int tableStartY = TITLE_HEIGHT + LINE_HEIGHT;
        // The horizontal table line
        w.printf("<line stroke=\"black\" stroke-width=\"1\" x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\"/>", tableStartX + t10, tableStartY, tableStartX + TABLE_WIDTH - t10, tableStartY);
        w.println();
        // The table column labels
        w.printf("<text style=\"text-anchor:middle\" x=\"%s\" y=\"%s\">x</text>", tableStartX + 3 * t10, tableStartY - LINE_HEIGHT / 4);
        w.println();
        w.printf("<text style=\"text-anchor:middle\" x=\"%s\" y=\"%s\">t (ms)</text>", tableStartX + 7 * t10, tableStartY - LINE_HEIGHT / 4);
        w.println();

        // The table data (x)
        w.printf("<text style=\"text-anchor:end\" y=\"%s\">", tableStartY);
        w.println();
        int lastY = tableStartY;
        for (DataPoint dp : data)
        {
          w.printf("<tspan dy=\"%s\" x=\"%s\">%s</tspan>", LINE_HEIGHT, tableStartX + 5 * t10 - 12, dp.m_x);
          w.println();
          lastY += 16;
        }
        w.println("</text>");

        // The vertical table line
        w.printf("<line stroke=\"black\" stroke-width=\"1\" x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\"/>", tableStartX + 5 * t10, tableStartY - LINE_HEIGHT - LINE_HEIGHT / 4, tableStartX + 5 * t10, lastY + LINE_HEIGHT / 4);
        w.println();

        // The table data (t)
        w.printf("<text style=\"text-anchor:start\" y=\"%s\">", tableStartY);
        w.println();
        for (DataPoint dp : data)
        {
          String text = new DecimalFormat("#.#####").format(dp.m_y);
          w.printf("<tspan dy=\"%s\" x=\"%s\">%s</tspan>", LINE_HEIGHT, tableStartX + 5 * t10 + 12, text);
          w.println();
        }
        w.println("</text>");

        // Legend
        int legendYPos = lastY + LINE_HEIGHT;
        // Legend line with crosses
        w.printf("<line stroke=\"blue\" stroke-width=\"1\" x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\"/>", tableStartX + 4 * t10, legendYPos, tableStartX + 6 * t10, legendYPos);
        w.println();
        drawCrossAt(w, tableStartX + 4 * t10, legendYPos);
        drawCrossAt(w, tableStartX + 6 * t10, legendYPos);
        // Legend text
        w.printf("<text style=\"text-anchor:middle;font-size:8pt\" y=\"%s\">", legendYPos + LINE_HEIGHT / 2);
        w.println();
        w.printf("<tspan x=\"%s\" dy=\"%s\">%s</tspan>", tableStartX + 5 * t10, LINE_HEIGHT, s_database);
        w.println();
        for (String backend : s_databaseBackends)
        {
          w.printf("<tspan x=\"%s\" dy=\"%s\">%s</tspan>", tableStartX + 5 * t10, LINE_HEIGHT, backend);
          w.println();
        }
        w.println();
        if (s_additionalInfo != null)
        {
          w.printf("<tspan x=\"%s\" dy=\"%s\">%s</tspan>", tableStartX + 5 * t10, LINE_HEIGHT, s_additionalInfo);
          w.println();
        }
        if (lgX)
        {
          w.printf("<tspan x=\"%s\" dy=\"%s\">Logarithmical x-axis</tspan>", tableStartX + 5 * t10, LINE_HEIGHT);
        }
        if (lgY)
        {
          w.printf("<tspan x=\"%s\" dy=\"%s\">Logarithmical y-axis</tspan>", tableStartX + 5 * t10, LINE_HEIGHT);
        }
        w.println("</text>");

        w.println("</g></svg>");
      }
      finally
      {
        w.close();
      }
    }
    catch (IOException e)
    {
      throw new WrappedIOException(e);
    }
    System.out.println("Wrote graph in " + f.getAbsolutePath());
  }

  private static void createGraphs(List<DataPoint> data, String fileName, String title1, String title2)
  {
    createGraph(data, fileName, title1, title2, false, false);
    createGraph(data, fileName + "_lgx", title1, title2, true, false);
    createGraph(data, fileName + "_lgy", title1, title2, false, true);
    createGraph(data, fileName + "_lgxy", title1, title2, true, true);
  }

  private static void createGraphs()
  {
    createGraphs(s_initialInserts, "initial_insert", "Insert x records in an initially empty database", "t = average time per insert");
    createGraphs(s_findRecords, "find", "Find x records using Database.get", "t = average time per search");
    createGraphs(s_iterateOverKeys, "key_iteration", "Iterate over x keys", "t = average time per key");
    createGraphs(s_iterateOverRecords, "record_iteration", "Iterate over x records", "t = average time per record");
    createGraphs(s_insertAdditionalRecords, "additional_insert", "Insert 5 new records in a database with x records", "t = average time per insert");
    createGraphs(s_updateAdditionalRecords, "additional_update", "Update last 5 records in a database with x + 5 records", "t = average time per update");
    createGraphs(s_deleteAdditionalRecords, "additional_delete", "Delete last 5 records from a database with x + 5 records", "t = average time per delete");
    createGraphs(s_updateRecords, "update", "Update x (all) records in a database with x records", "t = average time per update");
    createGraphs(s_deleteRecords, "delete", "Delete every 3rd record from a database with x records", "t = average time per delete");
    createGraphs(s_compact, "compact", "Compact database with 2x/3 records and x/3 holes", "t = total time");
    System.out.println();
  }

  private static void endTestConfigurationSuite()
  {
    System.out.println("Initial inserts:     | " + getDataPoints(s_initialInserts));
    System.out.println("Find records:        | " + getDataPoints(s_findRecords));
    System.out.println("Iterate over keys    | " + getDataPoints(s_iterateOverKeys));
    System.out.println("Iterate over records | " + getDataPoints(s_iterateOverRecords));
    System.out.println("Insert addl records  | " + getDataPoints(s_insertAdditionalRecords));
    System.out.println("Update addl records  | " + getDataPoints(s_updateAdditionalRecords));
    System.out.println("Delete addl records  | " + getDataPoints(s_deleteAdditionalRecords));
    System.out.println("Update records       | " + getDataPoints(s_updateRecords));
    System.out.println("Delete records       | " + getDataPoints(s_deleteRecords));
    System.out.println("Compact              | " + getDataPoints(s_compact));
    System.out.println();

    if (s_createGraphs)
    {
      createGraphs();
    }

    s_initialInserts.clear();
    s_findRecords.clear();
    s_iterateOverKeys.clear();
    s_iterateOverRecords.clear();
    s_insertAdditionalRecords.clear();
    s_updateAdditionalRecords.clear();
    s_deleteAdditionalRecords.clear();
    s_updateRecords.clear();
    s_deleteRecords.clear();
    s_compact.clear();
  }

  private static void parseInputArguments(String[] args)
  {
    for (int i = 0; i < args.length; i++)
    {
      if (args[i].equals("-g"))
      {
        System.out.println("Will create graphs");
        s_createGraphs = true;
      }
      else if (args[i].equals("-f"))
      {
        s_loadTestData = true;
        s_testDataFile = new File(args[++i]);
      }
      else
      {
        throw new RuntimeException("Unknown argument " + args[i]);
      }
    }
  }

  public static void main(String[] args) throws IOException
  {
    parseInputArguments(args);

    if (s_loadTestData)
    {
      loadTestData(s_testDataFile);
    }
    else
    {
      prepareTestData();
    }

    LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());

    List<TestConfiguration<?, ?>> testConfigurations = createTestConfigurations();
    for (TestConfiguration<?, ?> tc : testConfigurations)
    {
      if (tc == null)
      {
        // The end of a test configuration suite
        endTestConfigurationSuite();
      }
      else
      {
        s_graphFileNamePrefix = tc.getGraphFileNamePrefix();
        s_database = tc.getDatabaseImplementationName();
        s_databaseBackends = tc.getBackendImplementationNames();
        s_additionalInfo = tc.getAdditionalInfo();
        tc.setup(lah);
        try
        {
          System.out.println(tc.getHeader());
          System.out.println("================================================================================");
          if (tc instanceof CRSTestConfiguration)
          {
            runTestsFor((CRSTestConfiguration) tc);
          }
          else if (tc instanceof VRSTestConfiguration)
          {
            runTestsFor((VRSTestConfiguration) tc);
          }
          else
          {
            System.out.println("Error " + tc);
            return;
          }
          System.out.println();
        }
        catch (RuntimeException e)
        {
          saveTestData();
          throw e;
        }
        finally
        {
          tc.tearDown();
        }
      }
    }
  }
}
TOP

Related Classes of org.helidb.resources.perf.PerformanceTestRunner$DataPoint

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.