ydn.db
Version:
Javascript database library for IndexedDB, WebDatabase (WebSQL) and WebStorage (localStorage) storage mechanisms supporting version migration, advanced query and transaction workflow.
325 lines (287 loc) • 8.44 kB
JavaScript
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Performance test runner.
*
* @author kyawtun@yathit.com (Kyaw Tun)
*/
document.getElementById('version').textContent = ydn.db.version;
/**
* Performance test runner.
* @param {ydn.db.Storage} db database.
* @param {string} title test title.
* @param {number=} opt_nExp number of experiment. Default to 10.
* @constructor
*/
var Pref = function(db, title, opt_nExp) {
this.db = db;
db.addEventListener('ready', function() {
document.getElementById('mechanism').textContent = db.getType();
});
/**]
* @type {Array.<Test>}
* @private
*/
this.tests_ = [];
this.threads = [
db,
db.branch('atomic', true),
db.branch('single', true),
db.branch('multi', true),
db.branch('atomic', false),
db.branch('single', false),
db.branch('multi', false)
];
this.nRepeat = opt_nExp || 10; // number of experiment
this.title = title;
};
/**
* Create a test result row.
* @param {Test} test
* @param {Element} tbody
* @constructor
*/
var RowView = function(test, tbody) {
var tr = document.createElement('TR');
var webkit = /WebKit/.test(navigator.userAgent);
// details tag is only supported by webkit browser.
var disp = webkit ? '' : 'style="display: none;"';
var init = test.init ? '<p>Initialization function</p><pre>' +
test.init.toString() + '</pre>' : '';
tr.innerHTML = '<td><details><summary>' + test.title + '</summary>' +
'<div ' + disp + '>' + init +
'<p>Test function</p><pre>' +
test.test.toString() + '</pre></div></details></td>' +
'<td></td><td></td><td></td><td></td><td></td><td></td>';
tbody.appendChild(tr);
this.results_ = [[], [], [], [], [], [], []];
this.tx_counts_ = [[], [], [], [], [], [], []];
this.tr_ = tr;
};
RowView.std = function(mean, items) {
var deltaSquaredSum = 0;
for (var i = 0; i < items.length; i++) {
var delta = items[i] - mean;
deltaSquaredSum += delta * delta;
}
var variance = deltaSquaredSum / (items.length - 1);
return Math.sqrt(variance);
};
RowView.tDist = function(n) {
var tDistribution = [NaN, NaN, 12.71, 4.30, 3.18, 2.78, 2.57, 2.45, 2.36,
2.31, 2.26, 2.23, 2.20];
return tDistribution[n] || 2.20;
};
/**
* Add a new test result.
* @param {number} idx index of thread type.
* @param {number} op_sec operations per second.
* @param {number=} opt_tx_count number of transaction counts.
*/
RowView.prototype.addResult = function(idx, op_sec, opt_tx_count) {
if (idx == 0) {
return;
}
var scores = this.results_[idx];
var tx_counts = this.tx_counts_[idx];
var td = this.tr_.children[idx];
scores.push(op_sec);
if (opt_tx_count) {
tx_counts.push(opt_tx_count);
}
setTimeout(function() {
// update in separate thread.
var total = scores.reduce(function(x, p) {return x + p}, 0);
var tx_total = tx_counts.reduce(function(x, p) {return x + p}, 0);
var title = tx_total ? ' title="number of transactions used: ' +
(tx_total / tx_counts.length) + '"' : '';
var mean = (total / scores.length);
var count = scores.length;
var html = '<span' + title + '>' + (mean | 0) + '</span>';
if (count > 2) {
var sqrtCount = Math.sqrt(count);
var stdDev = RowView.std(mean, scores);
var stdErr = stdDev / sqrtCount;
var tDist = RowView.tDist(count);
// http://stackoverflow.com/questions/4448600
// http://www.webkit.org/perf/sunspider-0.9.1/sunspider-compare-results.js
var error = ' ± ' + ((tDist * stdErr / mean) * 100).toFixed(1) + '%';
html += '<sup>' + error + '</sup>';
}
td.innerHTML = html;
}, 10);
};
/**
* @param {Object} test test object.
* @param {Function} onFinished callback on finished the test.
*/
Pref.prototype.runTest = function(test, onFinished) {
var me = this;
var view = new RowView(test, this.tbody);
var onReady = function(data) {
var runRepeat = function(lap) {
if (lap == me.nRepeat) {
onFinished();
return;
}
lap++;
// run test for each thread.
var runTest = function(idx) {
var t1;
var db = me.threads[idx];
var onComplete = function(op_sec) {
var t2 = db.getTxNo();
var tx_count = t2 - t1;
view.addResult(idx, op_sec, tx_count);
idx++;
if (idx < me.threads.length) {
runTest(idx);
} else {
runRepeat(lap);
}
};
setTimeout(function() {
// give some time for database to complete previous job.
t1 = db.getTxNo();
test.run(db, data, onComplete);
}, 10);
};
runTest(0);
};
runRepeat(0);
};
if (test.init) {
test.init(function(data) {
me.prev_data_ = data;
onReady(data);
}, test.nData);
} else {
onReady(me.prev_data_);
}
};
/**
* Create a test.
* @param {string} title test title.
* @param {Function} test test function.
* @param {Function} init initialization function.
* @param {number} nExp number of experiment.
* @param {number=} nOp number of op. Default to 1.
* @param {number=} nData number of data. Default to nOp.
* @constructor
*/
Test = function(title, test, init, nExp, nOp, nData) {
this.title = title;
this.test = test;
this.init = init;
this.nExp = nExp;
this.nOp = nOp || 1;
this.nData = nData || nOp;
};
/**
* Prepare data for test.
* @param {*} data test group data.
* @return {*} by default reuse group data.
*/
Test.prototype.prepareData = function(data) {
return data;
};
Test.prototype.run = function(db, data, onComplete) {
var d = this.prepareData(data);
var start = + new Date();
var nop = this.nOp;
var on_complete = function() {
var end = + new Date();
var elapse = end - start;
onComplete(1000 * nop / elapse);
};
this.test(db, d, on_complete, this.nOp, this.nData);
};
/**
* @param {string} title test title.
* @param {Function} test test function.
* @param {Function} init initialization function.
* @param {number=} nOp number of op. Default to 1.
* @param {number=} nData number of data. Default to nOp.
*/
Pref.prototype.addTest = function(title, test, init, nOp, nData) {
var test = new Test(title, test, init, this.nRepeat, nOp, nData);
this.tests_.push(test);
return test;
};
/**
* To continue testing after this test suite.
* @param {ydn.db.Storage} db storage instance.
* @param {string} title title.
* @param {number=} opt_nExp number of experiment.
* @return {Pref}
*/
Pref.newPref = function(db, title, nExp) {
var pref = new Pref(db, title, nExp);
if (!Pref.prefs_) {
Pref.prefs_ = [];
}
Pref.prefs_.push(pref);
return pref;
};
Pref.run = function() {
if (Pref.running_) {
return;
}
Pref.running_ = true;
var run = function() {
var pref = Pref.prefs_.shift();
if (pref) {
pref.run(function() {
run();
})
} else {
console.log('All run.')
}
};
setTimeout(function() {
run();
}, 1);
};
Pref.prototype.tearDown = function() {
// clean up.
ydn.db.deleteDatabase(this.db.getName(), this.db.getType());
this.db.close();
};
/**
* @param {Function} cb callback on completing the run.
*/
Pref.prototype.run = function(cb) {
var test = this.tests_.shift();
var me = this;
if (!this.tbody) {
var table = document.getElementById('result-table');
var tr = document.createElement('tr');
tr.innerHTML = '<th colspan=7 class="title">' + (this.title || '') +
'</th>';
table.appendChild(tr);
this.tbody = document.createElement('tbody');
table.appendChild(this.tbody);
}
var onComplete = function() {
me.run(cb);
};
if (test) {
this.runTest(test, onComplete);
} else {
this.tearDown();
if (cb) {
cb();
}
}
};