From 26708b37c1e58046f380701b364b094136ce432f Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 May 2019 11:18:39 -0400 Subject: [PATCH] Integrate bare-bone filter hit stats in the logger Related issue: - https://github.com/gorhill/uBlock/issues/983 - https://github.com/gorhill/uBlock/issues/1353 The current implementation reports statistics for all static filters, and the presentation/featureset is intentionally minimal: *Do not open issues about this.* It's still a work in progress and it will be worked on slowly and thoughtfully over time and as time allows. Pausing the logger will not pause the collation of filter hit statistics, thus it is possible to lower the logger overhead by pausing logger output without losing filter hit collation. --- src/css/fa-icons.css | 3 + src/css/logger-ui.css | 61 ++++++++---- src/img/fontawesome/fontawesome-defs.svg | 1 + src/js/logger-ui.js | 118 ++++++++++++++++++++--- src/logger-ui.html | 40 ++++---- 5 files changed, 172 insertions(+), 51 deletions(-) diff --git a/src/css/fa-icons.css b/src/css/fa-icons.css index 372f68c6e..5f5c0817d 100644 --- a/src/css/fa-icons.css +++ b/src/css/fa-icons.css @@ -44,6 +44,9 @@ width: 1em; } +.fa-icon > .fa-icon_bar-chart { + width: calc(1em * 2048 / 1792); + } .fa-icon > .fa-icon_eraser, .fa-icon > .fa-icon_film { width: calc(1em * 1920 / 1792); diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index 7419f90ad..f37e28bdc 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -829,31 +829,25 @@ body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action > . margin-bottom: 0; } -#loggerSettingsDialog { +#loggerStatsDialog .sortedEntries { display: flex; flex-direction: column; + font-size: smaller; } -#loggerSettingsDialog > div { - padding-bottom: 1em; +#loggerStatsDialog .sortedEntries > div { + display: flex; } -#loggerSettingsDialog > div:last-of-type { - padding-bottom: 0; +#loggerStatsDialog .sortedEntries > div > span:first-of-type { + flex-grow: 0; + flex-shrink: 0; + padding: 0 2em 0 0; + text-align: right; + width: 3em; } -#loggerSettingsDialog ul { - padding: 0; - } -body[dir="ltr"] #loggerSettingsDialog ul { - padding-left: 2em; - } -body[dir="rtl"] #loggerSettingsDialog ul { - padding-right: 2em; - } -#loggerSettingsDialog li { - list-style-type: none; - margin: 0.5em 0 0 0; - } -#loggerSettingsDialog input { - max-width: 6em; +#loggerStatsDialog .sortedEntries > div > span:last-of-type { + flex-grow: 1; + flex-shrink: 1; + white-space: pre; } #loggerExportDialog { @@ -890,6 +884,33 @@ body[dir="rtl"] #loggerSettingsDialog ul { white-space: pre; } +#loggerSettingsDialog { + display: flex; + flex-direction: column; + } +#loggerSettingsDialog > div { + padding-bottom: 1em; + } +#loggerSettingsDialog > div:last-of-type { + padding-bottom: 0; + } +#loggerSettingsDialog ul { + padding: 0; + } +body[dir="ltr"] #loggerSettingsDialog ul { + padding-left: 2em; + } +body[dir="rtl"] #loggerSettingsDialog ul { + padding-right: 2em; + } +#loggerSettingsDialog li { + list-style-type: none; + margin: 0.5em 0 0 0; + } +#loggerSettingsDialog input { + max-width: 6em; + } + .hide { display: none !important; } diff --git a/src/img/fontawesome/fontawesome-defs.svg b/src/img/fontawesome/fontawesome-defs.svg index 7e72fe7c4..aae06408c 100644 --- a/src/img/fontawesome/fontawesome-defs.svg +++ b/src/img/fontawesome/fontawesome-defs.svg @@ -27,6 +27,7 @@ License - https://github.com/FortAwesome/Font-Awesome/tree/a8386aae19e200ddb0f68 + diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index b536831bc..040635366 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -263,6 +263,10 @@ const processLoggerEntries = function(response) { for ( const entry of entries ) { const unboxed = JSON.parse(entry); + if ( unboxed.filter instanceof Object ){ + loggerStats.processFilter(unboxed.filter); + } + if ( netInspectorPaused ) { continue; } const parsed = parseLogEntry(unboxed); if ( parsed.tabId !== undefined && @@ -384,7 +388,7 @@ const parseLogEntry = function(details) { /******************************************************************************/ -const viewPort = (function() { +const viewPort = (( ) => { const vwRenderer = document.getElementById('vwRenderer'); const vwScroller = document.getElementById('vwScroller'); const vwVirtualContent = document.getElementById('vwVirtualContent'); @@ -802,14 +806,14 @@ const viewPort = (function() { /******************************************************************************/ -const updateCurrentTabTitle = (function() { +const updateCurrentTabTitle = (( ) => { const i18nCurrentTab = vAPI.i18n('loggerCurrentTab'); return function() { const select = uDom.nodeFromId('pageSelector'); if ( select.value !== '_' || activeTabId === 0 ) { return; } const opt0 = select.querySelector('[value="_"]'); - const opt1 = select.querySelector('[value="' + activeTabId + '"]'); + const opt1 = select.querySelector(`[value="${activeTabId}"]`); let text = i18nCurrentTab; if ( opt1 !== null ) { text += ' / ' + opt1.textContent; @@ -934,9 +938,7 @@ const onLogBufferRead = function(response) { pageSelectorFromURLHash(); } - if ( netInspectorPaused === false ) { - processLoggerEntries(response); - } + processLoggerEntries(response); // Synchronize DOM with sent logger data document.body.classList.toggle( @@ -955,7 +957,7 @@ const onLogBufferRead = function(response) { /******************************************************************************/ -const readLogBuffer = (function() { +const readLogBuffer = (( ) => { let timer; const readLogBufferNow = function() { @@ -1011,7 +1013,7 @@ const pageSelectorChanged = function() { pageSelectorFromURLHash(); }; -const pageSelectorFromURLHash = (function() { +const pageSelectorFromURLHash = (( ) => { let lastHash; let lastSelectedTabId; @@ -1806,7 +1808,7 @@ const reloadTab = function(ev) { /******************************************************************************/ /******************************************************************************/ -const rowFilterer = (function() { +const rowFilterer = (( ) => { const userFilters = []; const builtinFilters = []; @@ -2017,7 +2019,7 @@ const rowFilterer = (function() { // - Max number of entry per distinct tab // - Max entry age -const rowJanitor = (function() { +const rowJanitor = (( ) => { const tabIdToDiscard = new Set(); const tabIdToLoadCountMap = new Map(); const tabIdToEntryCountMap = new Map(); @@ -2226,7 +2228,7 @@ const toggleVCompactView = function() { /******************************************************************************/ -const popupManager = (function() { +const popupManager = (( ) => { let realTabId = 0; let popup = null; let popupObserver = null; @@ -2316,7 +2318,95 @@ const popupManager = (function() { /******************************************************************************/ -(function() { +// Filter hit stats' MVP ("minimum viable product") +// +const loggerStats = (( ) => { + const filterHits = new Map(); + let dialog; + let timer; + + const makeRow = function() { + const div = document.createElement('div'); + div.appendChild(document.createElement('span')); + div.appendChild(document.createElement('span')); + return div; + }; + + const fillRow = function(div, entry) { + div.children[0].textContent = entry[1].toLocaleString(); + div.children[1].textContent = entry[0]; + }; + + const updateList = function() { + const sortedHits = Array.from(filterHits).sort((a, b) => { + return b[1] - a[1]; + }); + + const doc = document; + const parent = dialog.querySelector('.sortedEntries'); + let i = 0; + + // Reuse existing rows + for ( let iRow = 0; iRow < parent.childElementCount; iRow++ ) { + if ( i === sortedHits.length ) { break; } + fillRow(parent.children[iRow], sortedHits[i]); + i += 1; + } + + // Append new rows + if ( i < sortedHits.length ) { + const list = doc.createDocumentFragment(); + for ( ; i < sortedHits.length; i++ ) { + const div = makeRow(); + fillRow(div, sortedHits[i]); + list.appendChild(div); + } + parent.appendChild(list); + } + + // Remove extraneous rows + // [Should never happen at this point in this current + // bare-bone implementation] + }; + + const toggleOn = function() { + dialog = modalDialog.create( + '#loggerStatsDialog', + ( ) => { + dialog = undefined; + if ( timer !== undefined ) { + self.cancelIdleCallback(timer); + timer = undefined; + } + } + ); + updateList(); + modalDialog.show(); + }; + + uDom.nodeFromId('loggerStats').addEventListener('click', toggleOn); + + return { + processFilter: function(filter) { + if ( filter.source !== 'static' && filter.source !== 'cosmetic' ) { + return; + } + filterHits.set(filter.raw, (filterHits.get(filter.raw) || 0) + 1); + if ( dialog === undefined || timer !== undefined ) { return; } + timer = self.requestIdleCallback( + ( ) => { + timer = undefined; + updateList(); + }, + { timeout: 2001 } + ); + } + }; +})(); + +/******************************************************************************/ + +(( ) => { const lines = []; const options = { format: 'list', @@ -2505,7 +2595,7 @@ const popupManager = (function() { // - an option to discard immediately filtered out new entries // - max entry count _per load_ // -const loggerSettings = (function() { +const loggerSettings = (( ) => { const settings = { discard: { maxAge: 240, // global @@ -2611,7 +2701,7 @@ const loggerSettings = (function() { viewPort.updateLayout(); }; - uDom.nodeFromId('settings').addEventListener('click', toggleOn); + uDom.nodeFromId('loggerSettings').addEventListener('click', toggleOn); return settings; })(); diff --git a/src/logger-ui.html b/src/logger-ui.html index 3a1a61a6b..19271be50 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -70,8 +70,9 @@
+ bar-chart clipboard - cog + cog
@@ -159,6 +160,27 @@
+
+
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+ +
+
    @@ -178,22 +200,6 @@
-
-
-
- - -
-
- - -
-
- -
-
- -