const log = (...args)=>logImpl(2, ...args);
const warn = (...args)=>logImpl(1, ...args);
const error = (...args)=>logImpl(0, ...args);
- const metrics = Object.create(null);
- metrics.reset = ()=>{
- let k;
- const r = (m)=>(m.count = m.time = m.wait = 0);
- for(k in state.opIds){
- r(metrics[k] = Object.create(null));
- }
- let s = metrics.s11n = Object.create(null);
- s = s.serialize = Object.create(null);
- s.count = s.time = 0;
- s = metrics.s11n.deserialize = Object.create(null);
- s.count = s.time = 0;
- };
- metrics.dump = ()=>{
- let k, n = 0, t = 0, w = 0;
- for(k in state.opIds){
- const m = metrics[k];
- n += m.count;
- t += m.time;
- w += m.wait;
- m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
- }
- console.log(globalThis?.location?.href,
- "metrics for",globalThis?.location?.href,":\n",
- metrics,
- "\nTotal of",n,"op(s) for",t,"ms",
- "approx",w,"ms spent waiting on OPFS APIs.");
- console.log("Serialization metrics:",metrics.s11n);
- };
/**
__openFiles is a map of sqlite3_file pointers (integers) to
if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
};
- /**
- We track 2 different timers: the "metrics" timer records how much
- time we spend performing work. The "wait" timer records how much
- time we spend waiting on the underlying OPFS timer. See the calls
- to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd()
- throughout this file to see how they're used.
- */
- const __mTimer = Object.create(null);
- __mTimer.op = undefined;
- __mTimer.start = undefined;
- const mTimeStart = (op)=>{
- __mTimer.start = performance.now();
- __mTimer.op = op;
- //metrics[op] || toss("Maintenance required: missing metrics for",op);
- ++metrics[op].count;
- };
- const mTimeEnd = ()=>(
- metrics[__mTimer.op].time += performance.now() - __mTimer.start
- );
- const __wTimer = Object.create(null);
- __wTimer.op = undefined;
- __wTimer.start = undefined;
- const wTimeStart = (op)=>{
- __wTimer.start = performance.now();
- __wTimer.op = op;
- //metrics[op] || toss("Maintenance required: missing metrics for",op);
- };
- const wTimeEnd = ()=>(
- metrics[__wTimer.op].wait += performance.now() - __wTimer.start
- );
-
/**
Gets set to true by the 'opfs-async-shutdown' command to quit the
wait loop. This is only intended for debugging purposes: we cannot
/**
Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
- methods, as well as helpers like mkdir(). Maintenance reminder:
- members are in alphabetical order to simplify finding them.
+ methods, as well as helpers like mkdir().
*/
const vfsAsyncImpls = {
- 'opfs-async-metrics': async ()=>{
- mTimeStart('opfs-async-metrics');
- metrics.dump();
- storeAndNotify('opfs-async-metrics', 0);
- mTimeEnd();
- },
'opfs-async-shutdown': async ()=>{
flagAsyncShutdown = true;
storeAndNotify('opfs-async-shutdown', 0);
},
mkdir: async (dirname)=>{
- mTimeStart('mkdir');
let rc = 0;
- wTimeStart('mkdir');
try {
await getDirForFilename(dirname+"/filepart", true);
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR;
- }finally{
- wTimeEnd();
}
storeAndNotify('mkdir', rc);
- mTimeEnd();
},
xAccess: async (filename)=>{
- mTimeStart('xAccess');
/* OPFS cannot support the full range of xAccess() queries
sqlite3 calls for. We can essentially just tell if the file
is accessible, but if it is then it's automatically writable
accessible, non-0 means not accessible.
*/
let rc = 0;
- wTimeStart('xAccess');
try{
const [dh, fn] = await getDirForFilename(filename);
await dh.getFileHandle(fn);
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR;
- }finally{
- wTimeEnd();
}
storeAndNotify('xAccess', rc);
- mTimeEnd();
},
xClose: async function(fid/*sqlite3_file pointer*/){
const opName = 'xClose';
- mTimeStart(opName);
__implicitLocks.delete(fid);
const fh = __openFiles[fid];
let rc = 0;
- wTimeStart(opName);
if(fh){
delete __openFiles[fid];
await closeSyncHandle(fh);
state.s11n.serialize();
rc = state.sq3Codes.SQLITE_NOTFOUND;
}
- wTimeEnd();
storeAndNotify(opName, rc);
- mTimeEnd();
},
xDelete: async function(...args){
- mTimeStart('xDelete');
const rc = await vfsAsyncImpls.xDeleteNoWait(...args);
storeAndNotify('xDelete', rc);
- mTimeEnd();
},
xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){
/* The syncDir flag is, for purposes of the VFS API's semantics,
is false.
*/
let rc = 0;
- wTimeStart('xDelete');
try {
while(filename){
const [hDir, filenamePart] = await getDirForFilename(filename, false);
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR_DELETE;
}
- wTimeEnd();
return rc;
},
xFileSize: async function(fid/*sqlite3_file pointer*/){
- mTimeStart('xFileSize');
const fh = __openFiles[fid];
let rc = 0;
- wTimeStart('xFileSize');
try{
const sz = await (await getSyncHandle(fh,'xFileSize')).getSize();
state.s11n.serialize(Number(sz));
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR);
}
await releaseImplicitLock(fh);
- wTimeEnd();
storeAndNotify('xFileSize', rc);
- mTimeEnd();
},
xLock: async function(fid/*sqlite3_file pointer*/,
lockType/*SQLITE_LOCK_...*/){
- mTimeStart('xLock');
const fh = __openFiles[fid];
let rc = 0;
const oldLockType = fh.xLock;
fh.xLock = lockType;
if( !fh.syncHandle ){
- wTimeStart('xLock');
try {
await getSyncHandle(fh,'xLock');
__implicitLocks.delete(fid);
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK);
fh.xLock = oldLockType;
}
- wTimeEnd();
}
storeAndNotify('xLock',rc);
- mTimeEnd();
},
xOpen: async function(fid/*sqlite3_file pointer*/, filename,
flags/*SQLITE_OPEN_...*/,
opfsFlags/*OPFS_...*/){
const opName = 'xOpen';
- mTimeStart(opName);
const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags);
- wTimeStart('xOpen');
try{
let hDir, filenamePart;
try {
}catch(e){
state.s11n.storeException(1,e);
storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND);
- mTimeEnd();
- wTimeEnd();
return;
}
if( state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags ){
}
}
const hFile = await hDir.getFileHandle(filenamePart, {create});
- wTimeEnd();
const fh = Object.assign(Object.create(null),{
fid: fid,
filenameAbs: filename,
__openFiles[fid] = fh;
storeAndNotify(opName, 0);
}catch(e){
- wTimeEnd();
error(opName,e);
state.s11n.storeException(1,e);
storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR);
}
- mTimeEnd();
},
xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){
- mTimeStart('xRead');
let rc = 0, nRead;
const fh = __openFiles[fid];
try{
- wTimeStart('xRead');
nRead = (await getSyncHandle(fh,'xRead')).read(
fh.sabView.subarray(0, n),
{at: Number(offset64)}
);
- wTimeEnd();
if(nRead < n){/* Zero-fill remaining bytes */
fh.sabView.fill(0, nRead, n);
rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
}
}catch(e){
- if(undefined===nRead) wTimeEnd();
error("xRead() failed",e,fh);
state.s11n.storeException(1,e);
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_READ);
}
await releaseImplicitLock(fh);
storeAndNotify('xRead',rc);
- mTimeEnd();
},
xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){
- mTimeStart('xSync');
const fh = __openFiles[fid];
let rc = 0;
if(!fh.readOnly && fh.syncHandle){
try {
- wTimeStart('xSync');
await fh.syncHandle.flush();
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR_FSYNC;
}
- wTimeEnd();
}
storeAndNotify('xSync',rc);
- mTimeEnd();
},
xTruncate: async function(fid/*sqlite3_file pointer*/,size){
- mTimeStart('xTruncate');
let rc = 0;
const fh = __openFiles[fid];
- wTimeStart('xTruncate');
try{
affirmNotRO('xTruncate', fh);
await (await getSyncHandle(fh,'xTruncate')).truncate(size);
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_TRUNCATE);
}
await releaseImplicitLock(fh);
- wTimeEnd();
storeAndNotify('xTruncate',rc);
- mTimeEnd();
},
xUnlock: async function(fid/*sqlite3_file pointer*/,
lockType/*SQLITE_LOCK_...*/){
- mTimeStart('xUnlock');
let rc = 0;
const fh = __openFiles[fid];
if( fh.syncHandle
&& state.sq3Codes.SQLITE_LOCK_NONE===lockType
/* Note that we do not differentiate between lock types in
this VFS. We're either locked or unlocked. */ ){
- wTimeStart('xUnlock');
try { await closeSyncHandle(fh) }
catch(e){
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
}
- wTimeEnd();
}
storeAndNotify('xUnlock',rc);
- mTimeEnd();
},
xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
- mTimeStart('xWrite');
let rc;
const fh = __openFiles[fid];
- wTimeStart('xWrite');
try{
affirmNotRO('xWrite', fh);
rc = (
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE);
}
await releaseImplicitLock(fh);
- wTimeEnd();
storeAndNotify('xWrite',rc);
- mTimeEnd();
}
}/*vfsAsyncImpls*/;
}
};
state.s11n.deserialize = function(clear=false){
- ++metrics.s11n.deserialize.count;
- const t = performance.now();
const argc = viewU8[0];
const rc = argc ? [] : null;
if(argc){
}
if(clear) viewU8[0] = 0;
//log("deserialize:",argc, rc);
- metrics.s11n.deserialize.time += performance.now() - t;
return rc;
};
state.s11n.serialize = function(...args){
- const t = performance.now();
- ++metrics.s11n.serialize.count;
if(args.length){
//log("serialize():",args);
const typeIds = [];
}else{
viewU8[0] = 0;
}
- metrics.s11n.serialize.time += performance.now() - t;
};
state.s11n.storeException = state.asyncS11nExceptions
}
});
initS11n();
- metrics.reset();
log("init state",state);
wPost('opfs-async-inited');
waitLoop();
waitLoop();
}
break;
- case 'opfs-async-metrics':
- metrics.dump();
- break;
}
};
wPost('opfs-async-loaded');