aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/batch-runner.js
diff options
context:
space:
mode:
authorstephan <stephan@noemail.net>2022-08-30 17:57:50 +0000
committerstephan <stephan@noemail.net>2022-08-30 17:57:50 +0000
commit53f635df55ecfce3baf9fccc1909dad53bd9643e (patch)
tree20c9ffc69e147f846b4f6eb5447c182d0cca62e2 /ext/wasm/batch-runner.js
parentffc0cbb0248123ea635ad83428d5b4be11a8508b (diff)
downloadsqlite-53f635df55ecfce3baf9fccc1909dad53bd9643e.tar.gz
sqlite-53f635df55ecfce3baf9fccc1909dad53bd9643e.zip
batch-runner.js: add rudimentary metrics export to CSV. Add a toggle to reverse the log output mode (in normal order or most recent first).
FossilOrigin-Name: b26e2bc05537a1f451db1bdd36a824d71c3ea25a130b6285f31ff9799d65fa7a
Diffstat (limited to 'ext/wasm/batch-runner.js')
-rw-r--r--ext/wasm/batch-runner.js111
1 files changed, 107 insertions, 4 deletions
diff --git a/ext/wasm/batch-runner.js b/ext/wasm/batch-runner.js
index cbd712bb9..909a14004 100644
--- a/ext/wasm/batch-runner.js
+++ b/ext/wasm/batch-runner.js
@@ -26,10 +26,22 @@
btnRun: document.querySelector('#sql-run'),
btnRunNext: document.querySelector('#sql-run-next'),
btnRunRemaining: document.querySelector('#sql-run-remaining'),
+ btnExportMetrics: document.querySelector('#export-metrics'),
btnClear: document.querySelector('#output-clear'),
- btnReset: document.querySelector('#db-reset')
+ btnReset: document.querySelector('#db-reset'),
+ cbReverseLog: document.querySelector('#cb-reverse-log-order')
},
cache:{},
+ metrics:{
+ /**
+ Map of sql-file to timing metrics. We currently only store
+ the most recent run of each file, but we really should store
+ all runs so that we can average out certain values which vary
+ significantly across runs. e.g. a mandelbrot-generating query
+ will have a wide range of runtimes when run 10 times in a
+ row.
+ */
+ },
log: console.log.bind(console),
warn: console.warn.bind(console),
cls: function(){this.e.output.innerHTML = ''},
@@ -63,8 +75,11 @@
const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
const ppDb = wasm.scopedAllocPtr();
const rc = capi.sqlite3_open_v2(fn, ppDb, oFlags, null);
- if(rc) toss("sqlite3_open_v2() failed with code",rc);
pDb = wasm.getPtrValue(ppDb)
+ if(rc){
+ if(pDb) capi.sqlite3_close_v2(pDb);
+ toss("sqlite3_open_v2() failed with code",rc);
+ }
}finally{
wasm.scopedAllocPop(stack);
}
@@ -84,6 +99,12 @@
}
},
+ /**
+ Loads batch-runner.list and populates the selection list from
+ it. Returns a promise which resolves to nothing in particular
+ when it completes. Only intended to be run once at the start
+ of the app.
+ */
loadSqlList: async function(){
const sel = this.e.selSql;
sel.innerHTML = '';
@@ -156,7 +177,62 @@
document.querySelectorAll('.disable-during-eval').forEach((e)=>e.disabled = disable);
},
- /** Fetch ./fn and eval it as an SQL blob. */
+ /**
+ Converts this.metrics() to a form which is suitable for easy conversion to
+ CSV. It returns an array of arrays. The first sub-array is the column names.
+ The 2nd and subsequent are the values, one per test file (only the most recent
+ metrics are kept for any given file).
+ */
+ metricsToArrays: function(){
+ const rc = [];
+ Object.keys(this.metrics).sort().forEach((k)=>{
+ const m = this.metrics[k];
+ delete m.evalFileStart;
+ delete m.evalFileEnd;
+ const mk = Object.keys(m).sort();
+ if(!rc.length){
+ rc.push(['file', ...mk]);
+ }
+ const row = [k.split('/').pop()/*remove dir prefix from filename*/];
+ rc.push(row);
+ mk.forEach((kk)=>row.push(m[kk]));
+ });
+ return rc;
+ },
+
+ metricsToBlob: function(colSeparator='\t'){
+ const ar = [], ma = this.metricsToArrays();
+ if(!ma.length){
+ this.logErr("Metrics are empty. Run something.");
+ return;
+ }
+ ma.forEach(function(row){
+ ar.push(row.join(colSeparator),'\n');
+ });
+ return new Blob(ar);
+ },
+
+ downloadMetrics: function(){
+ const b = this.metricsToBlob();
+ if(!b) return;
+ const url = URL.createObjectURL(b);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'batch-runner-metrics-'+((new Date().getTime()/1000) | 0)+'.csv';
+ this.logHtml("Triggering download of",a.download);
+ document.body.appendChild(a);
+ a.click();
+ setTimeout(()=>{
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ }, 500);
+ },
+
+ /**
+ Fetch file fn and eval it as an SQL blob. This is an async
+ operation and returns a Promise which resolves to this
+ object on success.
+ */
evalFile: async function(fn){
const sql = await this.fetchFile(fn);
const banner = "========================================";
@@ -165,9 +241,11 @@
const capi = this.sqlite3.capi, wasm = capi.wasm;
let pStmt = 0, pSqlBegin;
const stack = wasm.scopedAllocPush();
- const metrics = Object.create(null);
+ const metrics = this.metrics[fn] = Object.create(null);
metrics.prepTotal = metrics.stepTotal = 0;
metrics.stmtCount = 0;
+ metrics.malloc = 0;
+ metrics.strcpy = 0;
this.blockControls(true);
if(this.gotErr){
this.logErr("Cannot run ["+fn+"]: error cleanup is pending.");
@@ -180,11 +258,16 @@
let t;
let sqlByteLen = sql.byteLength;
const [ppStmt, pzTail] = wasm.scopedAllocPtr(2);
+ t = performance.now();
pSqlBegin = wasm.alloc( sqlByteLen + 1/*SQL + NUL*/) || toss("alloc(",sqlByteLen,") failed");
+ metrics.malloc = performance.now() - t;
+ metrics.byteLength = sqlByteLen;
let pSql = pSqlBegin;
const pSqlEnd = pSqlBegin + sqlByteLen;
+ t = performance.now();
wasm.heap8().set(sql, pSql);
wasm.setMemValue(pSql + sqlByteLen, 0);
+ metrics.strcpy = performance.now() - t;
let breaker = 0;
while(pSql && wasm.getMemValue(pSql,'i8')){
wasm.setPtrValue(ppStmt, 0);
@@ -261,6 +344,20 @@
}
this.openDb(dbFile, !!pDir);
const who = this;
+ const eReverseLogNotice = document.querySelector('#reverse-log-notice');
+ if(this.e.cbReverseLog.checked){
+ eReverseLogNotice.classList.remove('hidden');
+ this.e.output.classList.add('reverse');
+ }
+ this.e.cbReverseLog.addEventListener('change', function(){
+ if(this.checked){
+ who.e.output.classList.add('reverse');
+ eReverseLogNotice.classList.remove('hidden');
+ }else{
+ who.e.output.classList.remove('reverse');
+ eReverseLogNotice.classList.add('hidden');
+ }
+ }, false);
this.e.btnClear.addEventListener('click', ()=>this.cls(), false);
this.e.btnRun.addEventListener('click', function(){
if(!who.e.selSql.value) return;
@@ -278,6 +375,12 @@
who.openDb(fn,true);
}
}, false);
+ this.e.btnExportMetrics.addEventListener('click', function(){
+ who.logHtml2('warning',"Metrics export is a Work in Progress. See output in dev console.");
+ who.downloadMetrics();
+ const m = who.metricsToArrays();
+ console.log("Metrics:",who.metrics, m);
+ });
this.e.btnRunRemaining.addEventListener('click', async function(){
let v = who.e.selSql.value;
const timeStart = performance.now();