diff --git a/background.js b/background.js index 781b9df..6ef7eb5 100644 --- a/background.js +++ b/background.js @@ -19,6 +19,8 @@ let queue = Promise.resolve(); let queuedCount = 0; let processing = false; let iconTimer = null; +let timingStats = { count: 0, mean: 0, m2: 0, total: 0 }; +let currentStart = 0; function setIcon(path) { if (browser.browserAction) { @@ -106,6 +108,7 @@ async function applyAiRules(idsInput) { updateActionIcon(); queue = queue.then(async () => { processing = true; + currentStart = Date.now(); queuedCount--; updateActionIcon(); try { @@ -141,9 +144,27 @@ async function applyAiRules(idsInput) { } } processing = false; + const elapsed = Date.now() - currentStart; + currentStart = 0; + const t = timingStats; + t.count += 1; + t.total += elapsed; + const delta = elapsed - t.mean; + t.mean += delta / t.count; + t.m2 += delta * (elapsed - t.mean); + await storage.local.set({ classifyStats: t }); showTransientIcon("resources/img/done.png"); } catch (e) { processing = false; + const elapsed = Date.now() - currentStart; + currentStart = 0; + const t = timingStats; + t.count += 1; + t.total += elapsed; + const delta = elapsed - t.mean; + t.mean += delta / t.count; + t.m2 += delta * (elapsed - t.mean); + await storage.local.set({ classifyStats: t }); logger.aiLog("failed to apply AI rules", { level: 'error' }, e); showTransientIcon("resources/img/error.png"); } @@ -199,6 +220,10 @@ async function clearCacheForMessages(idsInput) { logger.setDebug(store.debugLogging); await AiClassifier.setConfig(store); await AiClassifier.init(); + const savedStats = await storage.local.get('classifyStats'); + if (savedStats.classifyStats && typeof savedStats.classifyStats === 'object') { + Object.assign(timingStats, savedStats.classifyStats); + } aiRules = Array.isArray(store.aiRules) ? store.aiRules.map(r => { if (r.actions) return r; const actions = []; @@ -390,6 +415,16 @@ async function clearCacheForMessages(idsInput) { } } else if (msg?.type === "sortana:getQueueCount") { return { count: queuedCount + (processing ? 1 : 0) }; + } else if (msg?.type === "sortana:getTiming") { + const t = timingStats; + const std = t.count > 1 ? Math.sqrt(t.m2 / (t.count - 1)) : 0; + return { + count: queuedCount + (processing ? 1 : 0), + current: currentStart ? Date.now() - currentStart : -1, + average: t.mean, + total: t.total, + stddev: std + }; } else { logger.aiLog("Unknown message type, ignoring", {level: 'warn'}, msg?.type); } diff --git a/modules/AiClassifier.js b/modules/AiClassifier.js index de267b6..3c526f8 100644 --- a/modules/AiClassifier.js +++ b/modules/AiClassifier.js @@ -323,6 +323,13 @@ async function clearCache() { } } +async function getCacheSize() { + if (!gCacheLoaded) { + await loadCache(); + } + return gCache.size; +} + function classifyTextSync(text, criterion, cacheKey = null) { if (!Services?.tm?.spinEventLoopUntil) { throw new Error("classifyTextSync requires Services"); @@ -407,4 +414,4 @@ async function init() { await loadCache(); } -export { classifyText, classifyTextSync, setConfig, removeCacheEntries, clearCache, getReason, getCachedResult, buildCacheKey, buildCacheKeySync, init }; +export { classifyText, classifyTextSync, setConfig, removeCacheEntries, clearCache, getReason, getCachedResult, buildCacheKey, buildCacheKeySync, getCacheSize, init }; diff --git a/options/options.html b/options/options.html index 6ddefa7..ed1ef41 100644 --- a/options/options.html +++ b/options/options.html @@ -180,6 +180,9 @@