Commit f20bd798 authored by ericu@chromium.org's avatar ericu@chromium.org

Add more IDB perf tests, including cursor reads and log-style writes.

BUG=137764
TEST=


Review URL: https://chromiumcodereview.appspot.com/10826038

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149125 0039d316-1c4b-4281-b951-d872f2087c98
parent 51e153e8
......@@ -4,6 +4,7 @@
window.indexedDB = window.indexedDB || window.webkitIndexedDB ||
window.mozIndexedDB || window.msIndexedDB;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
var automation = {
results: {}
......@@ -59,7 +60,7 @@ var curVersion;
// indexIsMultiEntry: the "multiEntry" option for IDBIndexParameters
//
function createDatabase(
name, objectStoreNames, handler, errorHandler, options) {
name, objectStoreNames, handler, errorHandler, optionSets) {
var openRequest = indexedDB.open(name, baseVersion);
openRequest.onblocked = errorHandler;
function createObjectStores(db) {
......@@ -67,7 +68,10 @@ function createDatabase(
var name = objectStoreNames[store];
assert(!db.objectStoreNames.contains(name));
var os = db.createObjectStore(name);
if (options && options.indexName) {
if (optionSets) {
for (o in optionSets) {
var options = optionSets[o];
assert(options.indexName);
assert('indexKeyPath' in options);
os.createIndex(options.indexName, options.indexKeyPath,
{ unique: options.indexIsUnique,
......@@ -75,9 +79,10 @@ function createDatabase(
}
}
}
}
openRequest.onupgradeneeded = function(ev) {
// TODO: This is the spec-compliant path, which doesn't yet work in Chrome,
// and isn't yet tested, as this function won't currently be called.
// TODO(ericu): This is the spec-compliant path, which doesn't yet work in
// Chrome, and isn't yet tested, as this function won't currently be called.
assert(openRequest == ev.target);
createObjectStores(openRequest.result);
// onsuccess will get called after this exits.
......@@ -113,7 +118,7 @@ function alterObjectStores(
var version = curVersion + 1;
var openRequest = indexedDB.open(name, version);
openRequest.onblocked = errorHandler;
// TODO: This won't work in Firefox yet; see above in createDatabase.
// TODO(ericu): This won't work in Firefox yet; see above in createDatabase.
openRequest.onsuccess = function(ev) {
assert(openRequest == ev.target);
var db = openRequest.result;
......@@ -170,7 +175,7 @@ function getCompletionFunc(testName, startTime, onTestComplete) {
// Ignore the cleanup time for this test.
automation.addResult(testName, duration);
automation.setStatus("Deleting database.");
// TODO: Turn on actual deletion; for now it's way too slow.
// TODO(ericu): Turn on actual deletion; for now it's way too slow.
// deleteDatabase(testName, onDeleted);
onTestComplete();
}
......@@ -178,16 +183,62 @@ function getCompletionFunc(testName, startTime, onTestComplete) {
function getDisplayName(args) {
// The last arg is the completion callback the test runner tacks on.
// TODO(ericu): Make test errors delete the database automatically.
return getDisplayName.caller.name + "_" +
Array.prototype.slice.call(args, 0, args.length - 1).join("_");
}
// Pad a string [or object convertible to a string] to a fixed width; use this
// to have numeric strings sort properly.
function padToWidth(s, width) {
s = String(s);
assert(s.length <= width);
while (s.length < width) {
s = "0" + s;
}
return s;
}
function getSimpleKey(i) {
return "key " + i;
return "key " + padToWidth(i, 10);
}
function getSimpleValue(i) {
return "value " + i;
return "value " + padToWidth(i, 10);
}
function getForwardIndexKey(i) {
return i;
}
function getBackwardIndexKey(i) {
return -i;
}
// This is useful for indexing by keypath; the two names should be ordered in
// opposite directions for all i in uint32 range.
function getObjectValue(i) {
return {
firstName: getForwardIndexKey(i),
lastName: getBackwardIndexKey(i)
};
}
function getNFieldName(k) {
return "field" + k;
}
function getNFieldObjectValue(i, n) {
assert(Math.floor(n) == n);
assert(n > 0);
var o = {};
for (; n > 0; --n) {
// The value varies per field, each object will tend to be unique,
// and thanks to the modulus, indexing on different fields will give you
// different ordering for large-enough data sets.
o[getNFieldName(n - 1)] = Math.pow(i + 0.5, n + 0.5) % 65536;
}
return o;
}
function putLinearValues(
......@@ -238,6 +289,53 @@ function putRandomValues(
}
}
// getKey should be deterministic, as we assume that a cursor that starts at
// getKey(X) and runs through getKey(X + K) has exactly K values available.
// This is annoying to guarantee generally when using an index, so we avoid both
// ends of the key space just in case and use simple indices.
// TODO(ericu): Figure out if this can be simplified and we can remove uses of
// getObjectValue in favor of getNFieldObjectValue.
function getValuesFromCursor(
transaction, inputObjectStoreName, numReads, numKeys, indexName, getKey,
readKeysOnly, outputObjectStoreName) {
assert(2 * numReads < numKeys);
if (!getKey)
getKey = getSimpleKey;
var rand = Math.floor(Math.random() * (numKeys - 2 * numReads)) + numReads;
var values = [];
var queryObject = transaction.objectStore(inputObjectStoreName);
assert(queryObject);
if (indexName)
queryObject = queryObject.index(indexName);
var keyRange = IDBKeyRange.bound(
getKey(rand), getKey(rand + numReads), false, true);
var request;
if (readKeysOnly) {
request = queryObject.openKeyCursor(keyRange);
} else {
request = queryObject.openCursor(keyRange);
}
var oos;
if (outputObjectStoreName)
oos = transaction.objectStore(outputObjectStoreName);
var numReadsLeft = numReads;
request.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
assert(numReadsLeft);
--numReadsLeft;
if (oos) // Put in random order for maximum difficulty.
oos.put(cursor.value, Math.random());
values.push({key: cursor.key, value: cursor.value});
cursor.continue();
} else {
assert(!numReadsLeft);
}
}
request.onerror = onError;
}
function stringOfLength(n) {
assert(n > 0);
assert(n == Math.floor(n));
......
......@@ -51,8 +51,8 @@ function testRandomReadsAndWrites(
var completionFunc;
function onSetupComplete(db) {
automation.setStatus("Setup complete.");
runOneBatch(db);
completionFunc = getCompletionFunc(testName, Date.now(), onTestComplete);
runOneBatch(db);
}
function runOneBatch(db) {
......@@ -81,12 +81,14 @@ function testRandomReadsAndWrites(
}
automation.setStatus("Creating database.");
var options = {};
var options;
if (useIndexForReads) {
options.indexName = indexName;
options.indexKeyPath = "";
options.indexIsUnique = false;
options.indexIsMultiEntry = false;
options = [{
indexName: indexName,
indexKeyPath: "",
indexIsUnique: false,
indexIsMultiEntry: false,
}];
}
createDatabase(testName, objectStoreNames, onCreated, onError, options);
}
......@@ -135,21 +137,192 @@ function testCreateAndDeleteIndex(numKeys, onTestComplete) {
createDatabase(testName, objectStoreNames, onCreated, onError);
}
// TODO: Add a version that writes back to the same store, to see how that
// affects cursor speed w.r.t. invalidated caches.
function testCursorReadsAndRandomWrites(
readKeysOnly, useIndexForReads, writeToAnotherStore, onTestComplete) {
// There's no key cursor unless you're reading from an index.
assert(useIndexForReads || !readKeysOnly);
// If we're writing to another store, having an index would constrain our
// writes, as we create both object stores with the same configurations.
// We could do that if needed, but it's simpler not to.
assert(!useIndexForReads || !writeToAnotherStore);
var numKeys = 1000;
var numReadsPerTransaction = 100;
var testName = getDisplayName(arguments);
var objectStoreNames = ["input store"];
if (writeToAnotherStore)
objectStoreNames.push("output store");
var getKeyForRead = getSimpleKey;
var indexName;
if (useIndexForReads) {
indexName = "index";
getKeyForRead = function(i) {
// This depends on the implementations of getValuesFromCursor and
// getObjectValue. We reverse the order of the iteration here so that
// setting up bounds from k to k+n with n>0 works. Without this reversal,
// the upper bound is below the lower bound.
return getBackwardIndexKey(numKeys - i);
}
}
function onCreated(db) {
automation.setStatus("Setting up test database.");
var transaction = getTransaction(db, objectStoreNames, "readwrite",
function() { onSetupComplete(db); });
putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
getObjectValue);
}
function onSetupComplete(db) {
automation.setStatus("Setup complete.");
var completionFunc =
getCompletionFunc(testName, Date.now(), onTestComplete);
var mode = "readonly";
if (writeToAnotherStore)
mode = "readwrite";
var transaction =
getTransaction(db, objectStoreNames, mode, completionFunc);
getValuesFromCursor(
transaction, objectStoreNames[0], numReadsPerTransaction, numKeys,
indexName, getKeyForRead, readKeysOnly, objectStoreNames[1]);
}
automation.setStatus("Creating database.");
var options;
if (useIndexForReads) {
options = [{
indexName: indexName,
indexKeyPath: "lastName", // depends on getBackwardIndexKey()
indexIsUnique: true,
indexIsMultiEntry: false,
}];
}
createDatabase(testName, objectStoreNames, onCreated, onError, options);
}
function testSporadicWrites(
numWritesPerTransaction, numIndices, onTestComplete) {
var numKeys = 1000;
// With 30 transactions, spaced 50ms apart, we'll need at least 1.5s.
var numTransactions = 30;
var delayBetweenBatches = 50;
var indexName;
var testName = getDisplayName(arguments);
var numTransactionsLeft = numTransactions;
var objectStoreNames = ["store"];
var numTransactionsRunning = 0;
var getValue = getSimpleValue;
if (numIndices)
getValue = function (i) { return getNFieldObjectValue(i, numIndices); };
function onCreated(db) {
automation.setStatus("Setting up test database.");
var transaction = getTransaction(db, objectStoreNames, "readwrite",
function() { onSetupComplete(db); });
putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
getValue);
}
var completionFunc;
function onSetupComplete(db) {
automation.setStatus("Setup complete.");
completionFunc = getCompletionFunc(testName, Date.now(), onTestComplete);
runOneBatch(db);
}
function runOneBatch(db) {
assert(numTransactionsLeft);
if (--numTransactionsLeft) {
setTimeout(function () { runOneBatch(db); }, delayBetweenBatches);
}
++numTransactionsRunning;
function batchComplete() {
assert(numTransactionsRunning);
if (!--numTransactionsRunning && !numTransactionsLeft)
completionFunc();
}
var mode = "readonly";
if (numWritesPerTransaction)
mode = "readwrite";
var transaction = getTransaction(db, objectStoreNames, mode, batchComplete);
putRandomValues(transaction, objectStoreNames, numWritesPerTransaction,
numKeys);
}
automation.setStatus("Creating database.");
var options = [];
for (var i=0; i < numIndices; ++i) {
var o = {};
o.indexName = "index " + i;
o.indexKeyPath = getNFieldName(i);
o.indexIsUnique = false;
o.indexIsMultiEntry = false;
options.push(o);
}
createDatabase(testName, objectStoreNames, onCreated, onError, options);
}
var kUseIndex = true;
var kDontUseIndex = false;
var kReadKeysOnly = true;
var kReadDataToo = false;
var kWriteToo = true;
var kDontWrite = false;
var tests = [
// Create a single small item in a single object store.
[testCreateKeysInStores, 1, 1, 1],
// Create many small items in a single object store.
[testCreateKeysInStores, 100, 1, 1],
// Create a single small item in many object stores.
[testCreateKeysInStores, 1, 100, 1],
// Create many large items in a single object store.
[testCreateKeysInStores, 100, 1, 10000],
// Read a few random items in each of many transactions.
[testRandomReadsAndWrites, 1000, 5, 0, 50, kDontUseIndex],
// Read many random items in each of a few transactions.
[testRandomReadsAndWrites, 1000, 50, 0, 5, kDontUseIndex],
// Read many random items in each of a few transactions, in a large store.
[testRandomReadsAndWrites, 5000, 50, 0, 5, kDontUseIndex],
// Read a few random items from an index, in each of many transactions.
[testRandomReadsAndWrites, 1000, 5, 0, 50, kUseIndex],
// Read many random items from an index, in each of a few transactions.
[testRandomReadsAndWrites, 1000, 50, 0, 5, kUseIndex],
// Read many random items from an index, in each of a few transactions, in a
// large store.
[testRandomReadsAndWrites, 5000, 50, 0, 5, kUseIndex],
// Read and write a few random items in each of many transactions.
[testRandomReadsAndWrites, 1000, 5, 5, 50, kDontUseIndex],
// Read and write a few random items, reading from an index, in each of many
// transactions.
[testRandomReadsAndWrites, 1000, 5, 5, 50, kUseIndex],
// Read a long, contiguous sequence of an object store via a cursor.
[testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kDontWrite],
// Read a sequence of an object store via a cursor, writing
// transformed values into another.
[testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kWriteToo],
// Read a sequence of an index into an object store via a cursor.
[testCursorReadsAndRandomWrites, kReadDataToo, kUseIndex, kDontWrite],
// Read a sequence of an index into an object store via a key cursor.
[testCursorReadsAndRandomWrites, kReadKeysOnly, kUseIndex, kDontWrite],
// Make batches of random writes into a store, triggered by periodic setTimeout
// calls.
[testSporadicWrites, 5, 0],
// Make large batches of random writes into a store, triggered by periodic
// setTimeout calls.
[testSporadicWrites, 50, 0],
// Make batches of random writes into a store with many indices, triggered by
// periodic setTimeout calls.
[testSporadicWrites, 5, 10],
// Make large batches of random writes into a store with many indices, triggered
// by periodic setTimeout calls.
[testSporadicWrites, 50, 10],
// Create and delete an index on a store that already contains data [produces
// a timing result for each of creation and deletion].
[testCreateAndDeleteIndex, 1000]
];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment