1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-08 14:02:16 +03:00

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
This commit is contained in:
stephan
2022-08-30 17:57:50 +00:00
parent ffc0cbb024
commit 53f635df55
5 changed files with 141 additions and 17 deletions

View File

@@ -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();