Reuse test database to keep runtime reasonable.

This commit is contained in:
akwizgran
2017-11-06 13:41:57 +00:00
parent 341d18656d
commit 3178c16bac
3 changed files with 214 additions and 597 deletions

View File

@@ -8,6 +8,8 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import java.io.File; import java.io.File;
@@ -21,6 +23,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
public class TestUtils { public class TestUtils {
@@ -45,6 +49,13 @@ public class TestUtils {
return b; return b;
} }
public static byte[] getRandomBytes(int minLength, int maxLength) {
int length = minLength + random.nextInt(maxLength - minLength + 1);
byte[] b = new byte[length];
random.nextBytes(b);
return b;
}
public static byte[] getRandomId() { public static byte[] getRandomId() {
return getRandomBytes(UniqueId.LENGTH); return getRandomBytes(UniqueId.LENGTH);
} }
@@ -53,9 +64,17 @@ public class TestUtils {
return new SecretKey(getRandomBytes(SecretKey.LENGTH)); return new SecretKey(getRandomBytes(SecretKey.LENGTH));
} }
public static int getRandomLength(int min, int max) {
return min + random.nextInt(max - min + 1);
}
public static LocalAuthor getLocalAuthor() { public static LocalAuthor getLocalAuthor() {
return getLocalAuthor(getRandomLength(1, MAX_AUTHOR_NAME_LENGTH));
}
public static LocalAuthor getLocalAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(MAX_AUTHOR_NAME_LENGTH); String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
long created = System.currentTimeMillis(); long created = System.currentTimeMillis();
@@ -63,18 +82,39 @@ public class TestUtils {
} }
public static Author getAuthor() { public static Author getAuthor() {
return getAuthor(getRandomLength(1, MAX_AUTHOR_NAME_LENGTH));
}
public static Author getAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(MAX_AUTHOR_NAME_LENGTH); String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new Author(id, name, publicKey); return new Author(id, name, publicKey);
} }
public static Group getGroup(ClientId clientId) { public static Group getGroup(ClientId clientId) {
int descriptorLength = getRandomLength(1, MAX_GROUP_DESCRIPTOR_LENGTH);
return getGroup(clientId, descriptorLength);
}
public static Group getGroup(ClientId clientId, int descriptorLength) {
GroupId groupId = new GroupId(getRandomId()); GroupId groupId = new GroupId(getRandomId());
byte[] descriptor = getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH); byte[] descriptor = getRandomBytes(descriptorLength);
return new Group(groupId, clientId, descriptor); return new Group(groupId, clientId, descriptor);
} }
public static Message getMessage(GroupId groupId) {
int bodyLength = getRandomLength(1, MAX_MESSAGE_BODY_LENGTH);
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
}
public static Message getMessage(GroupId groupId, int rawLength) {
MessageId id = new MessageId(getRandomId());
byte[] raw = getRandomBytes(rawLength);
long timestamp = System.currentTimeMillis();
return new Message(id, groupId, timestamp, raw);
}
public static double getMedian(Collection<? extends Number> samples) { public static double getMedian(Collection<? extends Number> samples) {
int size = samples.size(); int size = samples.size();
if (size == 0) throw new IllegalArgumentException(); if (size == 0) throw new IllegalArgumentException();

View File

@@ -1,10 +1,6 @@
package org.briarproject.bramble.db; package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
interface BenchmarkTask<T> { interface BenchmarkTask<T> {
void prepareBenchmark(Database<T> db) throws DbException; void run(T context) throws Exception;
void runBenchmark(Database<T> db) throws DbException;
} }

View File

@@ -1,14 +1,15 @@
package org.briarproject.bramble.db; package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager.State; import org.briarproject.bramble.api.sync.ValidationManager.State;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.system.SystemClock; import org.briarproject.bramble.system.SystemClock;
@@ -23,24 +24,20 @@ import java.io.FileOutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.sql.Connection; import java.sql.Connection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getMean; import static org.briarproject.bramble.test.TestUtils.getMean;
import static org.briarproject.bramble.test.TestUtils.getMedian; import static org.briarproject.bramble.test.TestUtils.getMedian;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getStandardDeviation; import static org.briarproject.bramble.test.TestUtils.getStandardDeviation;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
@@ -50,33 +47,58 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
private static final int ONE_MEGABYTE = 1024 * 1024; private static final int ONE_MEGABYTE = 1024 * 1024;
private static final int MAX_SIZE = 100 * ONE_MEGABYTE; private static final int MAX_SIZE = 100 * ONE_MEGABYTE;
private static final int CLIENT_ID_LENGTH = 100;
private static final int METADATA_KEY_LENGTH = 100; /**
* How many contacts to simulate.
*/
private static final int CONTACTS = 20;
/**
* How many clients to simulate. Briar has nine: transport properties,
* introductions, messaging, forums, forum sharing, blogs,
* blog sharing, private groups, and private group sharing.
*/
private static final int CLIENTS = 10;
private static final int CLIENT_ID_LENGTH = 50;
/**
* How many groups to simulate for each contact. Briar has seven:
* transport properties, introductions, messaging, forum sharing, blog
* sharing, private group sharing, and the contact's blog.
*/
private static final int GROUPS_PER_CONTACT = 10;
/**
* How many local groups to simulate. Briar has three: transport
* properties, introductions and RSS feeds.
*/
private static final int LOCAL_GROUPS = 5;
private static final int MESSAGES_PER_GROUP = 20;
private static final int METADATA_KEYS_PER_GROUP = 5;
private static final int METADATA_KEYS_PER_MESSAGE = 5;
private static final int METADATA_KEY_LENGTH = 10;
private static final int METADATA_VALUE_LENGTH = 100; private static final int METADATA_VALUE_LENGTH = 100;
/** /**
* Skip test cases that create more than this many rows. * How many times to run each benchmark before measuring, to warm up the
* JIT and DB indices.
*/ */
private static final int MAX_ROWS = 100 * 1000; private static final int WARMUP_ITERATIONS = 1000;
/** /**
* How many times to run the benchmark before measuring, to warm up the JIT. * How many times to run each benchmark while measuring.
*/
private static final int WARMUP_ITERATIONS = 100;
/**
* How many times to run the benchmark while measuring.
*/ */
private static final int MEASUREMENT_ITERATIONS = 100; private static final int MEASUREMENT_ITERATIONS = 100;
/**
* How much time to allow for background operations to complete after
* preparing the benchmark.
*/
private static final int SLEEP_BEFORE_MEASUREMENT_MS = 500;
private final File testDir = getTestDirectory(); private final File testDir = getTestDirectory();
private final File resultsFile = getResultsFile(); private final File resultsFile = getResultsFile();
private final Random random = new Random();
private List<ClientId> clientIds;
private List<Group> groups;
private List<Message> messages;
private Map<GroupId, List<Metadata>> messageMeta;
protected abstract String getTestName(); protected abstract String getTestName();
@@ -98,626 +120,140 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
return new File(getTestName() + "-" + timestamp + ".tsv"); return new File(getTestName() + "-" + timestamp + ".tsv");
} }
@Test
public void testAddContact() throws Exception {
for (int contacts : new int[] {0, 1, 10, 100, 1000}) {
testAddContact(contacts);
}
}
private void testAddContact(final int contacts) throws Exception {
String name = "addContact(T, Author, AuthorId, boolean, boolean)";
Map<String, Object> args =
Collections.<String, Object>singletonMap("contacts", contacts);
benchmark(name, args, new BenchmarkTask<Connection>() {
private final LocalAuthor localAuthor = getLocalAuthor();
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.addLocalAuthor(txn, localAuthor);
for (int i = 0; i < contacts; i++) {
db.addContact(txn, getAuthor(), localAuthor.getId(), true,
true);
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.addContact(txn, getAuthor(), localAuthor.getId(), true,
true);
db.commitTransaction(txn);
}
});
}
@Test
public void testAddGroup() throws Exception {
for (int groups : new int[] {0, 1, 10, 100, 1000}) {
testAddGroup(groups);
}
}
private void testAddGroup(final int groups) throws Exception {
String name = "addGroup(T, group)";
Map<String, Object> args =
Collections.<String, Object>singletonMap("groups", groups);
benchmark(name, args, new BenchmarkTask<Connection>() {
private final ClientId clientId = getClientId();
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
for (int i = 0; i < groups; i++)
db.addGroup(txn, getGroup(clientId));
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.addGroup(txn, getGroup(clientId));
db.commitTransaction(txn);
}
});
}
@Test @Test
public void testGetContacts() throws Exception { public void testGetContacts() throws Exception {
for (int contacts : new int[] {1, 10, 100, 1000}) {
testGetContacts(contacts);
}
}
private void testGetContacts(final int contacts) throws Exception {
String name = "getContacts(T)"; String name = "getContacts(T)";
Map<String, Object> args = benchmark(name, db -> {
Collections.<String, Object>singletonMap("contacts", contacts); Connection txn = db.startTransaction();
db.getContacts(txn);
benchmark(name, args, new BenchmarkTask<Connection>() { db.commitTransaction(txn);
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
LocalAuthor localAuthor = getLocalAuthor();
Connection txn = db.startTransaction();
db.addLocalAuthor(txn, localAuthor);
for (int i = 0; i < contacts; i++) {
db.addContact(txn, getAuthor(), localAuthor.getId(), true,
true);
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getContacts(txn);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetRawMessage() throws Exception { public void testGetRawMessage() throws Exception {
for (int messages : new int[] {1, 100, 10000}) {
testGetRawMessage(messages);
}
}
private void testGetRawMessage(final int messages) throws Exception {
String name = "getRawMessage(T, MessageId)"; String name = "getRawMessage(T, MessageId)";
Map<String, Object> args = benchmark(name, db -> {
Collections.<String, Object>singletonMap("messages", messages); Connection txn = db.startTransaction();
db.getRawMessage(txn, pickRandom(messages).getId());
benchmark(name, args, new BenchmarkTask<Connection>() { db.commitTransaction(txn);
private MessageId messageId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
Group group = getGroup(getClientId());
Connection txn = db.startTransaction();
db.addGroup(txn, group);
for (int i = 0; i < messages; i++) {
Message m = getMessage(group.getId());
if (i == 0) messageId = m.getId();
db.addMessage(txn, m, DELIVERED, false);
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getRawMessage(txn, messageId);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetMessageIds() throws Exception { public void testGetMessageIds() throws Exception {
for (int groups : new int[] {1, 10, 100}) {
for (int messagesPerGroup : new int[] {1, 10, 100}) {
int rows = groups * messagesPerGroup;
if (rows > MAX_ROWS) continue;
testGetMessageIds(groups, messagesPerGroup);
}
}
}
private void testGetMessageIds(final int groups, final int messagesPerGroup)
throws Exception {
String name = "getMessageIds(T, GroupId)"; String name = "getMessageIds(T, GroupId)";
Map<String, Object> args = new LinkedHashMap<String, Object>(); benchmark(name, db -> {
args.put("groups", groups); Connection txn = db.startTransaction();
args.put("messagesPerGroup", messagesPerGroup); db.getMessageIds(txn, pickRandom(groups).getId());
db.commitTransaction(txn);
benchmark(name, args, new BenchmarkTask<Connection>() {
private GroupId groupId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
ClientId clientId = getClientId();
Connection txn = db.startTransaction();
for (int i = 0; i < groups; i++) {
Group g = getGroup(clientId);
if (i == 0) groupId = g.getId();
db.addGroup(txn, g);
for (int j = 0; j < messagesPerGroup; j++) {
Message m = getMessage(g.getId());
db.addMessage(txn, m, DELIVERED, false);
}
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getMessageIds(txn, groupId);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetMessageIdsWithQuery() throws Exception { public void testGetMessageIdsWithMatchingQuery() throws Exception {
for (int groups : new int[] {1, 10, 100}) { String name = "getMessageIds(T, GroupId, Metadata)";
for (int messagesPerGroup : new int[] {1, 10, 100}) { benchmark(name, db -> {
for (int keysPerMessage : new int[] {1, 10, 100}) { Connection txn = db.startTransaction();
int rows = groups * messagesPerGroup * keysPerMessage; GroupId g = pickRandom(groups).getId();
if (rows > MAX_ROWS) continue; db.getMessageIds(txn, g, pickRandom(messageMeta.get(g)));
for (int keysPerQuery : new int[] {1, 10}) { db.commitTransaction(txn);
testGetMessageIdsWithQuery(groups, messagesPerGroup, });
keysPerMessage, keysPerQuery);
}
}
}
}
} }
private void testGetMessageIdsWithQuery(final int groups, @Test
final int messagesPerGroup, final int keysPerMessage, public void testGetMessageIdsWithNonMatchingQuery() throws Exception {
final int keysPerQuery) throws Exception {
String name = "getMessageIds(T, GroupId, Metadata)"; String name = "getMessageIds(T, GroupId, Metadata)";
Map<String, Object> args = new LinkedHashMap<String, Object>(); benchmark(name, db -> {
args.put("groups", groups); Connection txn = db.startTransaction();
args.put("messagesPerGroup", messagesPerGroup); Metadata query = getMetadata(METADATA_KEYS_PER_MESSAGE);
args.put("keysPerMessage", keysPerMessage); db.getMessageIds(txn, pickRandom(groups).getId(), query);
args.put("keysPerQuery", keysPerQuery); db.commitTransaction(txn);
benchmark(name, args, new BenchmarkTask<Connection>() {
private final Metadata query = getMetadata(keysPerQuery);
private GroupId groupId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
ClientId clientId = getClientId();
Connection txn = db.startTransaction();
for (int i = 0; i < groups; i++) {
Group g = getGroup(clientId);
if (i == 0) groupId = g.getId();
db.addGroup(txn, g);
for (int j = 0; j < messagesPerGroup; j++) {
Message m = getMessage(g.getId());
db.addMessage(txn, m, DELIVERED, false);
Metadata meta = getMetadata(keysPerMessage);
db.mergeMessageMetadata(txn, m.getId(), meta);
}
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getMessageIds(txn, groupId, query);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetGroupMetadata() throws Exception { public void testGetGroupMetadata() throws Exception {
for (int groups : new int[] {1, 10, 100, 1000}) {
for (int keysPerGroup : new int[] {1, 10, 100}) {
int rows = groups * keysPerGroup;
if (rows > MAX_ROWS) continue;
testGetGroupMetadata(groups, keysPerGroup);
}
}
}
private void testGetGroupMetadata(final int groups, final int keysPerGroup)
throws Exception {
String name = "getGroupMetadata(T, GroupId)"; String name = "getGroupMetadata(T, GroupId)";
Map<String, Object> args = new LinkedHashMap<String, Object>(); benchmark(name, db -> {
args.put("groups", groups); Connection txn = db.startTransaction();
args.put("keysPerGroup", keysPerGroup); db.getGroupMetadata(txn, pickRandom(groups).getId());
db.commitTransaction(txn);
benchmark(name, args, new BenchmarkTask<Connection>() {
private GroupId groupId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
ClientId clientId = getClientId();
Connection txn = db.startTransaction();
for (int i = 0; i < groups; i++) {
Group g = getGroup(clientId);
if (i == 0) groupId = g.getId();
db.addGroup(txn, g);
Metadata meta = getMetadata(keysPerGroup);
db.mergeGroupMetadata(txn, g.getId(), meta);
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getGroupMetadata(txn, groupId);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetMessageMetadataForGroup() throws Exception { public void testGetMessageMetadataForGroup() throws Exception {
for (int groups : new int[] {1, 10, 100}) {
for (int messagesPerGroup : new int[] {1, 10, 100}) {
for (int keysPerMessage : new int[] {1, 10, 100}) {
int rows = groups * messagesPerGroup * keysPerMessage;
if (rows > MAX_ROWS) continue;
testGetMessageMetadataForGroup(groups, messagesPerGroup,
keysPerMessage);
}
}
}
}
private void testGetMessageMetadataForGroup(final int groups,
final int messagesPerGroup, final int keysPerMessage)
throws Exception {
String name = "getMessageMetadata(T, GroupId)"; String name = "getMessageMetadata(T, GroupId)";
Map<String, Object> args = new LinkedHashMap<String, Object>(); benchmark(name, db -> {
args.put("groups", groups); Connection txn = db.startTransaction();
args.put("messagesPerGroup", messagesPerGroup); db.getMessageMetadata(txn, pickRandom(groups).getId());
args.put("keysPerMessage", keysPerMessage); db.commitTransaction(txn);
benchmark(name, args, new BenchmarkTask<Connection>() {
private GroupId groupId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
ClientId clientId = getClientId();
Connection txn = db.startTransaction();
for (int i = 0; i < groups; i++) {
Group g = getGroup(clientId);
if (i == 0) groupId = g.getId();
db.addGroup(txn, g);
for (int j = 0; j < messagesPerGroup; j++) {
Message m = getMessage(g.getId());
db.addMessage(txn, m, DELIVERED, false);
Metadata meta = getMetadata(keysPerMessage);
db.mergeMessageMetadata(txn, m.getId(), meta);
}
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getMessageMetadata(txn, groupId);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetMessageMetadataForMessage() throws Exception { public void testGetMessageMetadataForMessage() throws Exception {
for (int messages : new int[] {1, 100, 10000}) {
for (int keysPerMessage : new int[] {1, 10, 100}) {
int rows = messages * keysPerMessage;
if (rows > MAX_ROWS) continue;
testGetMessageMetadataForMessage(messages, keysPerMessage);
}
}
}
private void testGetMessageMetadataForMessage(final int messages,
final int keysPerMessage) throws Exception {
String name = "getMessageMetadata(T, MessageId)"; String name = "getMessageMetadata(T, MessageId)";
Map<String, Object> args = new LinkedHashMap<String, Object>(); benchmark(name, db -> {
args.put("messages", messages); Connection txn = db.startTransaction();
args.put("keysPerMessage", keysPerMessage); db.getMessageMetadata(txn, pickRandom(messages).getId());
db.commitTransaction(txn);
benchmark(name, args, new BenchmarkTask<Connection>() {
private MessageId messageId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
Group g = getGroup(getClientId());
Connection txn = db.startTransaction();
db.addGroup(txn, g);
for (int i = 0; i < messages; i++) {
Message m = getMessage(g.getId());
if (i == 0) messageId = m.getId();
db.addMessage(txn, m, DELIVERED, false);
Metadata meta = getMetadata(keysPerMessage);
db.mergeMessageMetadata(txn, m.getId(), meta);
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getMessageMetadata(txn, messageId);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetMessagesToShare() throws Exception { public void testGetMessagesToShare() throws Exception {
for (int clients : new int[] {1, 10, 100}) {
for (int groupsPerClient : new int[] {1, 10, 100}) {
for (int messagesPerGroup : new int[] {1, 10, 100}) {
int rows = clients * groupsPerClient * messagesPerGroup;
if (rows > MAX_ROWS) continue;
testGetMessagesToShare(clients, groupsPerClient,
messagesPerGroup);
}
}
}
}
private void testGetMessagesToShare(final int clients,
final int groupsPerClient, final int messagesPerGroup)
throws Exception {
String name = "getMessagesToShare(T, ClientId)"; String name = "getMessagesToShare(T, ClientId)";
Map<String, Object> args = new LinkedHashMap<String, Object>(); benchmark(name, db -> {
args.put("clients", clients); Connection txn = db.startTransaction();
args.put("groupsPerClient", groupsPerClient); db.getMessagesToShare(txn, pickRandom(clientIds));
args.put("messagesPerGroup", messagesPerGroup); db.commitTransaction(txn);
benchmark(name, args, new BenchmarkTask<Connection>() {
private ClientId clientId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
Random random = new Random();
Connection txn = db.startTransaction();
for (int i = 0; i < clients; i++) {
ClientId c = getClientId();
if (i == 0) clientId = c;
for (int j = 0; j < groupsPerClient; j++) {
Group g = getGroup(c);
db.addGroup(txn, g);
MessageId lastMessageId = null;
for (int k = 0; k < messagesPerGroup; k++) {
Message m = getMessage(g.getId());
boolean shared = random.nextBoolean();
db.addMessage(txn, m, DELIVERED, shared);
if (lastMessageId != null) {
db.addMessageDependency(txn, g.getId(),
m.getId(), lastMessageId);
}
lastMessageId = m.getId();
}
}
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getMessagesToShare(txn, clientId);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetMessagesToValidate() throws Exception { public void testGetMessagesToValidate() throws Exception {
for (int clients : new int[] {1, 10, 100}) {
for (int groupsPerClient : new int[] {1, 10, 100}) {
for (int messagesPerGroup : new int[] {1, 10, 100}) {
int rows = clients * groupsPerClient * messagesPerGroup;
if (rows > MAX_ROWS) continue;
testGetMessagesToValidate(clients, groupsPerClient,
messagesPerGroup);
}
}
}
}
private void testGetMessagesToValidate(final int clients,
final int groupsPerClient, final int messagesPerGroup)
throws Exception {
String name = "getMessagesToValidate(T, ClientId)"; String name = "getMessagesToValidate(T, ClientId)";
Map<String, Object> args = new LinkedHashMap<String, Object>(); benchmark(name, db -> {
args.put("clients", clients); Connection txn = db.startTransaction();
args.put("groupsPerClient", groupsPerClient); db.getMessagesToValidate(txn, pickRandom(clientIds));
args.put("messagesPerGroup", messagesPerGroup); db.commitTransaction(txn);
benchmark(name, args, new BenchmarkTask<Connection>() {
private ClientId clientId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
Random random = new Random();
Connection txn = db.startTransaction();
for (int i = 0; i < clients; i++) {
ClientId c = getClientId();
if (i == 0) clientId = c;
for (int j = 0; j < groupsPerClient; j++) {
Group g = getGroup(c);
db.addGroup(txn, g);
for (int k = 0; k < messagesPerGroup; k++) {
Message m = getMessage(g.getId());
State s = random.nextBoolean() ? UNKNOWN : PENDING;
db.addMessage(txn, m, s, false);
}
}
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getMessagesToValidate(txn, clientId);
db.commitTransaction(txn);
}
}); });
} }
@Test @Test
public void testGetPendingMessages() throws Exception { public void testGetPendingMessages() throws Exception {
for (int clients : new int[] {1, 10, 100}) {
for (int groupsPerClient : new int[] {1, 10, 100}) {
for (int messagesPerGroup : new int[] {1, 10, 100}) {
int rows = clients * groupsPerClient * messagesPerGroup;
if (rows > MAX_ROWS) continue;
testGetPendingMessages(clients, groupsPerClient,
messagesPerGroup);
}
}
}
}
private void testGetPendingMessages(final int clients,
final int groupsPerClient, final int messagesPerGroup)
throws Exception {
String name = "getPendingMessages(T, ClientId)"; String name = "getPendingMessages(T, ClientId)";
Map<String, Object> args = new LinkedHashMap<String, Object>(); benchmark(name, db -> {
args.put("clients", clients); Connection txn = db.startTransaction();
args.put("groupsPerClient", groupsPerClient); db.getPendingMessages(txn, pickRandom(clientIds));
args.put("messagesPerGroup", messagesPerGroup); db.commitTransaction(txn);
benchmark(name, args, new BenchmarkTask<Connection>() {
private ClientId clientId;
@Override
public void prepareBenchmark(Database<Connection> db)
throws DbException {
Random random = new Random();
Connection txn = db.startTransaction();
for (int i = 0; i < clients; i++) {
ClientId c = getClientId();
if (i == 0) clientId = c;
for (int j = 0; j < groupsPerClient; j++) {
Group g = getGroup(c);
db.addGroup(txn, g);
for (int k = 0; k < messagesPerGroup; k++) {
Message m = getMessage(g.getId());
State s = random.nextBoolean() ? UNKNOWN : PENDING;
db.addMessage(txn, m, s, false);
}
}
}
db.commitTransaction(txn);
}
@Override
public void runBenchmark(Database<Connection> db)
throws DbException {
Connection txn = db.startTransaction();
db.getPendingMessages(txn, clientId);
db.commitTransaction(txn);
}
}); });
} }
private void benchmark(String name, Map<String, Object> args, private <T> T pickRandom(List<T> list) {
BenchmarkTask<Connection> task) throws Exception { return list.get(random.nextInt(list.size()));
for (int i = 0; i < WARMUP_ITERATIONS; i++) { }
Database<Connection> db = open();
try { private void benchmark(String name,
task.prepareBenchmark(db); BenchmarkTask<Database<Connection>> task) throws Exception {
task.runBenchmark(db); deleteTestDirectory(testDir);
} finally { Database<Connection> db = openDatabase();
db.close(); populateDatabase(db);
} db.close();
} db = openDatabase();
List<Long> durations = new ArrayList<Long>(MEASUREMENT_ITERATIONS); List<Long> durations = new ArrayList<>(MEASUREMENT_ITERATIONS);
for (int i = 0; i < WARMUP_ITERATIONS; i++) task.run(db);
for (int i = 0; i < MEASUREMENT_ITERATIONS; i++) { for (int i = 0; i < MEASUREMENT_ITERATIONS; i++) {
Database<Connection> db = open(); long start = System.nanoTime();
try { task.run(db);
task.prepareBenchmark(db); durations.add(System.nanoTime() - start);
Thread.sleep(SLEEP_BEFORE_MEASUREMENT_MS);
long start = System.nanoTime();
task.runBenchmark(db);
durations.add(System.nanoTime() - start);
} finally {
db.close();
}
} }
double meanMillis = getMean(durations) / 1000 / 1000; db.close();
double medianMillis = getMedian(durations) / 1000 / 1000; String result = String.format("%s\t%,d\t%,d\t%,d", name,
double stdDevMillis = getStandardDeviation(durations) / 1000 / 1000; (long) getMean(durations), (long) getMedian(durations),
String result = name + '\t' + args + '\t' + meanMillis (long) getStandardDeviation(durations));
+ '\t' + medianMillis + '\t' + stdDevMillis;
System.out.println(result); System.out.println(result);
PrintWriter out = PrintWriter out =
new PrintWriter(new FileOutputStream(resultsFile, true), true); new PrintWriter(new FileOutputStream(resultsFile, true), true);
@@ -725,23 +261,68 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
out.close(); out.close();
} }
private Database<Connection> open() throws DbException { private Database<Connection> openDatabase() throws DbException {
deleteTestDirectory(testDir);
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock()); new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
db.open(); db.open();
return db; return db;
} }
private ClientId getClientId() { private void populateDatabase(Database<Connection> db) throws DbException {
return new ClientId(getRandomString(CLIENT_ID_LENGTH)); clientIds = new ArrayList<>();
groups = new ArrayList<>();
messages = new ArrayList<>();
messageMeta = new HashMap<>();
for (int i = 0; i < CLIENTS; i++) clientIds.add(getClientId());
Connection txn = db.startTransaction();
LocalAuthor localAuthor = getLocalAuthor();
db.addLocalAuthor(txn, localAuthor);
for (int i = 0; i < CONTACTS; i++) {
Author a = getAuthor();
ContactId contactId = db.addContact(txn, a, localAuthor.getId(),
random.nextBoolean(), true);
for (int j = 0; j < GROUPS_PER_CONTACT; j++) {
Group g = getGroup(clientIds.get(j % CLIENTS));
groups.add(g);
messageMeta.put(g.getId(), new ArrayList<>());
db.addGroup(txn, g);
db.addGroupVisibility(txn, contactId, g.getId(), true);
Metadata gm = getMetadata(METADATA_KEYS_PER_GROUP);
db.mergeGroupMetadata(txn, g.getId(), gm);
for (int k = 0; k < MESSAGES_PER_GROUP; k++) {
Message m = getMessage(g.getId());
messages.add(m);
State state = State.fromValue(random.nextInt(4));
db.addMessage(txn, m, state, random.nextBoolean());
Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
messageMeta.get(g.getId()).add(mm);
db.mergeMessageMetadata(txn, m.getId(), mm);
}
}
}
for (int i = 0; i < LOCAL_GROUPS; i++) {
Group g = getGroup(clientIds.get(i % CLIENTS));
groups.add(g);
messageMeta.put(g.getId(), new ArrayList<>());
db.addGroup(txn, g);
Metadata gm = getMetadata(METADATA_KEYS_PER_GROUP);
db.mergeGroupMetadata(txn, g.getId(), gm);
for (int j = 0; j < MESSAGES_PER_GROUP; j++) {
Message m = getMessage(g.getId());
messages.add(m);
db.addMessage(txn, m, DELIVERED, false);
Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
messageMeta.get(g.getId()).add(mm);
db.mergeMessageMetadata(txn, m.getId(), mm);
}
}
db.commitTransaction(txn);
} }
private Message getMessage(GroupId groupId) { private ClientId getClientId() {
MessageId id = new MessageId(getRandomId()); return new ClientId(getRandomString(CLIENT_ID_LENGTH));
byte[] raw = getRandomBytes(MAX_MESSAGE_LENGTH);
long timestamp = System.currentTimeMillis();
return new Message(id, groupId, timestamp, raw);
} }
private Metadata getMetadata(int keys) { private Metadata getMetadata(int keys) {