todosapp: refactor benchmark setup, extract createHierarchy helper
Change-Id: I76624212448276a7f30142eb7a5d730abba7315a
diff --git a/browser/benchmark.js b/browser/benchmark.js
index 9fe0187..52a73a2 100644
--- a/browser/benchmark.js
+++ b/browser/benchmark.js
@@ -7,34 +7,13 @@
var nosql = syncbase.nosql;
var util = require('./util');
-
-exports.logFn = logFn;
-exports.runBenchmark = runBenchmark;
+var wt = util.wt;
var LOG_EVERYTHING = false;
-function logStart(name) {
- console.log(name + ' start');
- return Date.now();
-}
-
-function logStop(name, start, err) {
- var dt = Date.now() - start;
- console.log(name + (err ? ' FAILED after ' : ' took ') + dt + 'ms');
- if (err) console.error(err);
-}
-
-function logFn(name, cb) {
- var start = logStart(name);
- return function(err) {
- logStop(name, start, err);
- cb.apply(null, arguments);
- };
-}
-
// Does n parallel puts with a common prefix, then returns the prefix.
function doPuts(ctx, tb, n, cb) {
- cb = logFn('doPuts', cb);
+ cb = util.logFn('doPuts', cb);
var prefix = util.timestamp() + '.';
async.times(100, function(n, cb) {
// TODO(sadovsky): Remove this once we loosen Syncbase's naming rules.
@@ -51,9 +30,9 @@
});
}
-// Scans (and logs) all records with the given prefix.
+// Scans all records with the given prefix.
function doScan(ctx, tb, prefix, cb) {
- cb = logFn('doScan(' + prefix + ')', cb);
+ cb = util.logFn('doScan(' + prefix + ')', cb);
var bytes = 0, streamErr = null;
tb.scan(ctx, nosql.rowrange.prefix(prefix), function(err) {
err = err || streamErr;
@@ -68,12 +47,18 @@
});
}
-// Assumes table 'tb' exists.
-function runBenchmark(ctx, db, cb) {
- cb = logFn('runBenchmark', cb);
- var tb = db.table('tb');
- doPuts(ctx, tb, 100, function(err, prefix) {
+// Creates a fresh hierarchy, then runs doPuts followed by doScan.
+exports.runBenchmark = function(rt, name, cb) {
+ cb = util.logFn('runBenchmark', cb);
+ var ctx = rt.getContext();
+ var s = syncbase.newService(name);
+ var appName = 'bm.' + util.uid();
+ util.createHierarchy(ctx, s, appName, 'db', 'tb', function(err, db) {
if (err) return cb(err);
- doScan(ctx, tb, prefix, cb);
+ var tb = db.table('tb');
+ doPuts(wt(ctx), tb, 100, function(err, prefix) {
+ if (err) return cb(err);
+ doScan(wt(ctx), tb, prefix, cb);
+ });
});
-}
+};
diff --git a/browser/collection_dispatcher.js b/browser/collection_dispatcher.js
index 1986dc2..79c3717 100644
--- a/browser/collection_dispatcher.js
+++ b/browser/collection_dispatcher.js
@@ -67,6 +67,7 @@
cb = cb || noop;
return function(err) {
cb.apply(null, arguments);
- if (!err) that.emit('change');
+ if (err) return;
+ that.emit('change');
};
};
diff --git a/browser/defaults.js b/browser/defaults.js
index 2dceddb..6a876f3 100644
--- a/browser/defaults.js
+++ b/browser/defaults.js
@@ -5,10 +5,10 @@
var vanadium = require('vanadium');
var verror = vanadium.verror;
-var bm = require('./benchmark');
var CollectionDispatcher = require('./collection_dispatcher');
var MemCollection = require('./mem_collection');
var SyncbaseDispatcher = require('./syncbase_dispatcher');
+var util = require('./util');
// Copied from meteor/todos/server/bootstrap.js.
var data = [
@@ -49,7 +49,7 @@
];
function initData(disp, cb) {
- cb = bm.logFn('initData', cb);
+ cb = util.logFn('initData', cb);
var timestamp = Date.now();
async.each(data, function(list, cb) {
disp.addList({name: list.name}, function(err, listId) {
@@ -73,52 +73,28 @@
});
}
-// Returns a new Vanadium context object with a timeout.
-function wt(ctx, timeout) {
- return ctx.withTimeout(timeout || 5000);
-}
-
-exports.initSyncbaseDispatcher = function(rt, name, benchmark, cb) {
- cb = bm.logFn('initSyncbaseDispatcher', cb);
+exports.initSyncbaseDispatcher = function(rt, name, cb) {
+ cb = util.logFn('initSyncbaseDispatcher', cb);
var ctx = rt.getContext();
- var service = syncbase.newService(name);
- var app = service.app('todos'), db = app.noSqlDatabase('db');
- var disp = new SyncbaseDispatcher(rt, db);
- app.create(wt(ctx), {}, function(err) {
- if (err) {
- if (err instanceof verror.ExistError) {
- if (benchmark) {
- return bm.runBenchmark(ctx, db, cb);
- }
- console.log('app exists; assuming database has been initialized');
- return cb(null, disp);
- }
+ var s = syncbase.newService(name);
+ util.createHierarchy(ctx, s, 'todos', 'db', 'tb', function(err, db) {
+ if (err && !(err instanceof verror.ExistError)) {
return cb(err);
}
- console.log('app did not exist; initializing database');
- db.create(wt(ctx), {}, function(err) {
+ var disp = new SyncbaseDispatcher(rt, db);
+ if (err) { // verror.ExistError
+ console.log('skipping initData');
+ return cb(null, disp);
+ }
+ initData(disp, function(err) {
if (err) return cb(err);
- db.createTable(wt(ctx), 'tb', {}, function(err) {
- if (err) return cb(err);
- if (benchmark) {
- // TODO(sadovsky): Restructure things so that we still call initData
- // even if on the first page load we ran the benchmark. Maybe do this
- // by having the benchmark use a completely different app and/or
- // database.
- return bm.runBenchmark(ctx, db, cb);
- }
- console.log('hierarchy created; writing rows');
- initData(disp, function(err) {
- if (err) return cb(err);
- cb(null, disp);
- });
- });
+ cb(null, disp);
});
});
};
exports.initCollectionDispatcher = function(cb) {
- cb = bm.logFn('initCollectionDispatcher', cb);
+ cb = util.logFn('initCollectionDispatcher', cb);
var lists = new MemCollection('lists'), todos = new MemCollection('todos');
var disp = new CollectionDispatcher(lists, todos);
initData(disp, function(err) {
diff --git a/browser/dom_log.js b/browser/dom_log.js
index fa38ac3..e7beb8b 100644
--- a/browser/dom_log.js
+++ b/browser/dom_log.js
@@ -31,7 +31,7 @@
window.onerror = function(errorMsg, url, lineNumber) {
var args = [util.timestamp(), errorMsg];
append('msg error', args.join(' '));
- // Show log if it's not already visible, so that the developer sees the
+ // Show the log if it's not already visible, so that the developer sees the
// error.
logEl.classList.add('visible');
};
diff --git a/browser/index.js b/browser/index.js
index dd5d88d..5b9b82c 100644
--- a/browser/index.js
+++ b/browser/index.js
@@ -13,7 +13,8 @@
var bm = require('./benchmark');
var defaults = require('./defaults');
var domLog = require('./dom_log');
-var h = require('./util').h;
+var util = require('./util');
+var h = util.h;
////////////////////////////////////////
// Constants
@@ -37,7 +38,7 @@
function noop() {}
function initVanadium(cb) {
- cb = bm.logFn('initVanadium', cb);
+ cb = util.logFn('initVanadium', cb);
var vanadiumConfig = {
logLevel: vanadium.vlog.levels.INFO,
namespaceRoots: u.query.mounttable ? [u.query.mounttable] : undefined,
@@ -46,7 +47,7 @@
vanadium.init(vanadiumConfig, cb);
}
-function initDispatcher(dispType, syncbaseName, benchmark, cb) {
+function initDispatcher(dispType, syncbaseName, cb) {
var clientCb = cb;
cb = function(err, resDisp) {
if (err) return clientCb(err);
@@ -54,13 +55,12 @@
clientCb();
};
if (dispType === DISP_TYPE_COLLECTION) {
- console.assert(!benchmark);
defaults.initCollectionDispatcher(cb);
} else if (dispType === DISP_TYPE_SYNCBASE) {
initVanadium(function(err, rt) {
if (err) return cb(err);
userEmail = blessingToEmail(rt.accountName);
- defaults.initSyncbaseDispatcher(rt, syncbaseName, benchmark, cb);
+ defaults.initSyncbaseDispatcher(rt, syncbaseName, cb);
});
} else {
process.nextTick(function() {
@@ -401,12 +401,12 @@
if (shared) {
// TODO(sadovsky): Let the user add members to an existing SG once
// Syncbase supports it.
- console.error('Cannot add members to existing SyncGroup.');
+ alert('Cannot add members to an existing SyncGroup.');
return;
}
// TODO(sadovsky): Better input validation.
if (!value.includes('@') || !value.includes('.')) {
- console.error('Invalid email address.');
+ alert('Invalid email address.');
return;
}
disp.createSyncGroup(list._id, [
@@ -531,18 +531,22 @@
},
componentWillMount: function() {
var that = this, props = this.props;
- var dt = props.dispType, sn = props.syncbaseName, bm = props.benchmark;
+ if (props.benchmark) {
+ initVanadium(function(err, rt) {
+ if (err) throw err;
+ bm.runBenchmark(rt, props.syncbaseName, function(err) {
+ if (err) throw err;
+ });
+ });
+ return;
+ }
function done() {
that.setState({dispInitialized: true});
}
- initDispatcher(dt, sn, bm, function(err) {
+ initDispatcher(props.dispType, props.syncbaseName, function(err) {
if (err) throw err;
- if (bm) {
- console.log('benchmark done');
- return;
- }
if (props.joinListId) {
- console.assert(dt === DISP_TYPE_SYNCBASE);
+ console.assert(props.dispType === DISP_TYPE_SYNCBASE);
disp.joinSyncGroup(props.joinListId, function(err) {
// Note, joinSyncGroup is a noop (no error) if the caller is already a
// member, which is the desired behavior here.
@@ -623,7 +627,7 @@
// TODO(sadovsky): Only read (and only redraw) what's needed based on what
// changed.
disp.on('change', function() {
- var doneCb = bm.logFn('onChange', function(err) {
+ var doneCb = util.logFn('onChange', function(err) {
if (err) throw err;
});
updateLists(function(err) {
diff --git a/browser/syncbase_dispatcher.js b/browser/syncbase_dispatcher.js
index 89ac9b3..8895f8e 100644
--- a/browser/syncbase_dispatcher.js
+++ b/browser/syncbase_dispatcher.js
@@ -20,14 +20,14 @@
var _ = require('lodash');
var async = require('async');
var inherits = require('inherits');
-var randomBytes = require('randombytes');
var syncbase = require('syncbase');
var nosql = syncbase.nosql;
var vanadium = require('vanadium');
var vtrace = vanadium.vtrace;
-var bm = require('./benchmark');
var Dispatcher = require('./dispatcher');
+var util = require('./util');
+var wn = util.wn, wt = util.wt;
inherits(SyncbaseDispatcher, Dispatcher);
module.exports = SyncbaseDispatcher;
@@ -61,17 +61,12 @@
return args.join(SEP);
}
-function uuid(len) {
- len = len || 16;
- return randomBytes(Math.ceil(len / 2)).toString('hex').substr(0, len);
-}
-
function newListKey() {
- return uuid();
+ return util.uid();
}
function newTodoKey(listId) {
- return join(listId, 'todos', uuid());
+ return join(listId, 'todos', util.uid());
}
function tagKey(todoId, tag) {
@@ -95,19 +90,6 @@
return JSON.parse(x);
}
-////////////////////////////////////////
-// Vanadium helpers
-
-// Returns a new Vanadium context object with a timeout.
-function wt(ctx, timeout) {
- return ctx.withTimeout(timeout || 5000);
-}
-
-// Returns a new Vanadium context object with the given name.
-function wn(ctx, name) {
- return vtrace.withNewSpan(ctx, name);
-}
-
var SILENT = new vanadium.context.ContextKey();
// Defines a SyncbaseDispatcher method. If the first argument to fn is not a
@@ -131,7 +113,7 @@
// to JSON, drop square brackets.
var cb = args[args.length - 1];
var argsStr = JSON.stringify(args.slice(1, -1)).slice(1, -1);
- args[args.length - 1] = bm.logFn(name + '(' + argsStr + ')', cb);
+ args[args.length - 1] = util.logFn(name + '(' + argsStr + ')', cb);
}
return fn.apply(this, args);
};
@@ -396,7 +378,7 @@
// Random number, used to implement watch. Each client writes to their own watch
// key to signify that they've written new data, and each client periodically
// polls all watch keys to see if anything has changed.
-var clientId = uuid();
+var clientId = util.uid();
function watchPrefix(listId) {
return join(listId, 'watch');
}
diff --git a/browser/util.js b/browser/util.js
index 0584383..1234937 100644
--- a/browser/util.js
+++ b/browser/util.js
@@ -2,7 +2,9 @@
var _ = require('lodash');
var moment = require('moment');
+var randomBytes = require('randombytes');
var React = require('react');
+var vtrace = require('vanadium').vtrace;
exports.h = function(selector, props, children) {
if (_.isPlainObject(props)) {
@@ -27,3 +29,62 @@
t = t || Date.now();
return moment(t).format('HH:mm:ss.SSS');
};
+
+// Returns a unique string identifier of the given length.
+exports.uid = function(len) {
+ len = len || 16;
+ return randomBytes(Math.ceil(len / 2)).toString('hex').substr(0, len);
+};
+
+function logStart(name) {
+ console.log(name + ' start');
+ return Date.now();
+}
+
+function logStop(name, start, err) {
+ var dt = Date.now() - start;
+ console.log(name + (err ? ' FAILED after ' : ' took ') + dt + 'ms');
+ if (err) console.error(err);
+}
+
+// Wraps the given callback to log start time, stop time, and delta time of a
+// function invocation.
+function logFn(name, cb) {
+ var start = logStart(name);
+ return function(err) {
+ logStop(name, start, err);
+ cb.apply(null, arguments);
+ };
+}
+exports.logFn = logFn;
+
+// Returns a new Vanadium context object with the given name.
+exports.wn = function(ctx, name) {
+ return vtrace.withNewSpan(ctx, name);
+};
+
+// Returns a new Vanadium context object with a timeout.
+function wt(ctx, timeout) {
+ return ctx.withTimeout(timeout || 5000);
+}
+exports.wt = wt;
+
+// Creates <app>/<database>/<table> hierarchy in Syncbase.
+// Note, for errors we still return the db handle since some errors (e.g.
+// verror.ExistError) are non-fatal.
+exports.createHierarchy = function(ctx, service, appName, dbName, tbName, cb) {
+ var app = service.app(appName), db = app.noSqlDatabase(dbName);
+ var appLog = 'create app "' + appName + '"';
+ app.create(wt(ctx), {}, logFn(appLog, function(err) {
+ if (err) return cb(err, db);
+ var dbLog = 'create database "' + dbName + '"';
+ db.create(wt(ctx), {}, logFn(dbLog, function(err) {
+ if (err) return cb(err, db);
+ var tbLog = 'create table "' + tbName + '"';
+ db.createTable(wt(ctx), tbName, {}, logFn(tbLog, function(err) {
+ if (err) return cb(err, db);
+ cb(null, db);
+ }));
+ }));
+ }));
+};