Skip to content

Commit

Permalink
Group all benchmarking functions in a separate file
Browse files Browse the repository at this point in the history
Related issue:
- uBlockOrigin/uBlock-issues#1664

The various filtering engine benchmarking functions are best
isolated in their own file since they have specific
dependencies that should not be suffered by the filtering
engines.

Additionally, moved decomposeHostname() into uri-utils.js
as it's a hostname-related function required by many
filtering engine cores -- this allows to further reduce
or outright remove dependency on `µb`.
  • Loading branch information
gorhill committed Jul 29, 2021
1 parent 62b6826 commit 8ef8c5a
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 376 deletions.
7 changes: 0 additions & 7 deletions src/js/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,13 +312,6 @@ const µBlock = { // jshint ignore:line
return super.getTabOrigin();
}

getTabHostname() {
if ( this.tabHostname === undefined ) {
this.tabHostname = hostnameFromURI(this.getTabOrigin());
}
return super.getTabHostname();
}

toLogger() {
this.tstamp = Date.now();
if ( this.domain === undefined ) {
Expand Down
298 changes: 298 additions & 0 deletions src/js/benchmarks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/

'use strict';

/******************************************************************************/

import cosmeticFilteringEngine from './cosmetic-filtering.js';
import globals from './globals.js';
import io from './assets.js';
import scriptletFilteringEngine from './scriptlet-filtering.js';
import staticNetFilteringEngine from './static-net-filtering.js';
import µb from './background.js';
import { FilteringContext } from './filtering-context.js';
import { sessionFirewall } from './dynamic-net-filtering.js';
import { LineIterator } from './text-iterators.js';

import {
domainFromHostname,
entityFromDomain,
hostnameFromURI,
} from './uri-utils.js';

/******************************************************************************/

// The requests.json.gz file can be downloaded from:
// https://cdn.cliqz.com/adblocking/requests_top500.json.gz
//
// Which is linked from:
// https://whotracks.me/blog/adblockers_performance_study.html
//
// Copy the file into ./tmp/requests.json.gz
//
// If the file is present when you build uBO using `make-[target].sh` from
// the shell, the resulting package will have `./assets/requests.json`, which
// will be looked-up by the method below to launch a benchmark session.
//
// From uBO's dev console, launch the benchmark:
// µBlock.staticNetFilteringEngine.benchmark();
//
// The usual browser dev tools can be used to obtain useful profiling
// data, i.e. start the profiler, call the benchmark method from the
// console, then stop the profiler when it completes.
//
// Keep in mind that the measurements at the blog post above where obtained
// with ONLY EasyList. The CPU reportedly used was:
// https://www.cpubenchmark.net/cpu.php?cpu=Intel+Core+i7-6600U+%40+2.60GHz&id=2608
//
// Rename ./tmp/requests.json.gz to something else if you no longer want
// ./assets/requests.json in the build.

const loadBenchmarkDataset = (( ) => {
let datasetPromise;
let ttlTimer;

return function() {
if ( ttlTimer !== undefined ) {
globals.clearTimeout(ttlTimer);
ttlTimer = undefined;
}

globals.setTimeout(( ) => {
ttlTimer = undefined;
datasetPromise = undefined;
}, 5 * 60 * 1000);

if ( datasetPromise !== undefined ) {
return datasetPromise;
}

const datasetURL = µb.hiddenSettings.benchmarkDatasetURL;
if ( datasetURL === 'unset' ) {
console.info(`No benchmark dataset available.`);
return Promise.resolve();
}
console.info(`Loading benchmark dataset...`);
datasetPromise = io.fetchText(datasetURL).then(details => {
console.info(`Parsing benchmark dataset...`);
const requests = [];
const lineIter = new LineIterator(details.content);
while ( lineIter.eot() === false ) {
let request;
try {
request = JSON.parse(lineIter.next());
} catch(ex) {
}
if ( request instanceof Object === false ) { continue; }
if ( !request.frameUrl || !request.url ) { continue; }
if ( request.cpt === 'document' ) {
request.cpt = 'main_frame';
} else if ( request.cpt === 'xhr' ) {
request.cpt = 'xmlhttprequest';
}
requests.push(request);
}
return requests;
}).catch(details => {
console.info(`Not found: ${details.url}`);
datasetPromise = undefined;
});

return datasetPromise;
};
})();

/******************************************************************************/

// action: 1=test

µb.benchmarkStaticNetFiltering = async function(options = {}) {
const { target, redirectEngine } = options;

const requests = await loadBenchmarkDataset();
if ( Array.isArray(requests) === false || requests.length === 0 ) {
const text = 'No dataset found to benchmark';
console.info(text);
return text;
}

console.info(`Benchmarking staticNetFilteringEngine.matchRequest()...`);

const fctxt = new FilteringContext();

if ( typeof target === 'number' ) {
const request = requests[target];
fctxt.setURL(request.url);
fctxt.setDocOriginFromURL(request.frameUrl);
fctxt.setType(request.cpt);
const r = staticNetFilteringEngine.matchRequest(fctxt);
console.info(`Result=${r}:`);
console.info(`\ttype=${fctxt.type}`);
console.info(`\turl=${fctxt.url}`);
console.info(`\tdocOrigin=${fctxt.getDocOrigin()}`);
if ( r !== 0 ) {
console.info(staticNetFilteringEngine.toLogData());
}
return;
}

const t0 = globals.performance.now();
let matchCount = 0;
let blockCount = 0;
let allowCount = 0;
for ( let i = 0; i < requests.length; i++ ) {
const request = requests[i];
fctxt.setURL(request.url);
fctxt.setDocOriginFromURL(request.frameUrl);
fctxt.setType(request.cpt);
staticNetFilteringEngine.redirectURL = undefined;
const r = staticNetFilteringEngine.matchRequest(fctxt);
matchCount += 1;
if ( r === 1 ) { blockCount += 1; }
else if ( r === 2 ) { allowCount += 1; }
if ( r !== 1 ) {
if ( staticNetFilteringEngine.hasQuery(fctxt) ) {
staticNetFilteringEngine.filterQuery(fctxt, 'queryprune');
}
if ( fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) {
staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp');
}
staticNetFilteringEngine.matchHeaders(fctxt, []);
} else if ( redirectEngine !== undefined ) {
staticNetFilteringEngine.redirectRequest(redirectEngine, fctxt);
}
}
const t1 = globals.performance.now();
const dur = t1 - t0;

const output = [
'Benchmarked static network filtering engine:',
`\tEvaluated ${matchCount} match calls in ${dur.toFixed(0)} ms`,
`\tAverage: ${(dur / matchCount).toFixed(3)} ms per request`,
`\tNot blocked: ${matchCount - blockCount - allowCount}`,
`\tBlocked: ${blockCount}`,
`\tUnblocked: ${allowCount}`,
];
const s = output.join('\n');
console.info(s);
return s;
};

/******************************************************************************/

µb.benchmarkDynamicNetFiltering = async function() {
const requests = await loadBenchmarkDataset();
if ( Array.isArray(requests) === false || requests.length === 0 ) {
console.info('No requests found to benchmark');
return;
}
console.info(`Benchmarking sessionFirewall.evaluateCellZY()...`);
const fctxt = new FilteringContext();
const t0 = globals.performance.now();
for ( const request of requests ) {
fctxt.setURL(request.url);
fctxt.setTabOriginFromURL(request.frameUrl);
fctxt.setType(request.cpt);
sessionFirewall.evaluateCellZY(
fctxt.getTabHostname(),
fctxt.getHostname(),
fctxt.type
);
}
const t1 = globals.performance.now();
const dur = t1 - t0;
console.info(`Evaluated ${requests.length} requests in ${dur.toFixed(0)} ms`);
console.info(`\tAverage: ${(dur / requests.length).toFixed(3)} ms per request`);
};

/******************************************************************************/

µb.benchmarkCosmeticFiltering = async function() {
const requests = await loadBenchmarkDataset();
if ( Array.isArray(requests) === false || requests.length === 0 ) {
console.info('No requests found to benchmark');
return;
}
console.info('Benchmarking cosmeticFilteringEngine.retrieveSpecificSelectors()...');
const details = {
tabId: undefined,
frameId: undefined,
hostname: '',
domain: '',
entity: '',
};
const options = {
noSpecificCosmeticFiltering: false,
noGenericCosmeticFiltering: false,
};
let count = 0;
const t0 = globals.performance.now();
for ( let i = 0; i < requests.length; i++ ) {
const request = requests[i];
if ( request.cpt !== 'main_frame' ) { continue; }
count += 1;
details.hostname = hostnameFromURI(request.url);
details.domain = domainFromHostname(details.hostname);
details.entity = entityFromDomain(details.domain);
void cosmeticFilteringEngine.retrieveSpecificSelectors(details, options);
}
const t1 = globals.performance.now();
const dur = t1 - t0;
console.info(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`);
console.info(`\tAverage: ${(dur / count).toFixed(3)} ms per request`);
};

/******************************************************************************/

µb.benchmarkScriptletFiltering = async function() {
const requests = await loadBenchmarkDataset();
if ( Array.isArray(requests) === false || requests.length === 0 ) {
console.info('No requests found to benchmark');
return;
}
console.info('Benchmarking scriptletFilteringEngine.retrieve()...');
const details = {
domain: '',
entity: '',
hostname: '',
tabId: 0,
url: '',
};
let count = 0;
const t0 = globals.performance.now();
for ( let i = 0; i < requests.length; i++ ) {
const request = requests[i];
if ( request.cpt !== 'main_frame' ) { continue; }
count += 1;
details.url = request.url;
details.hostname = hostnameFromURI(request.url);
details.domain = domainFromHostname(details.hostname);
details.entity = entityFromDomain(details.domain);
void scriptletFilteringEngine.retrieve(details);
}
const t1 = globals.performance.now();
const dur = t1 - t0;
console.info(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`);
console.info(`\tAverage: ${(dur / count).toFixed(3)} ms per request`);
};

/******************************************************************************/
43 changes: 0 additions & 43 deletions src/js/cosmetic-filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,6 @@ import {
StaticExtFilteringSessionDB,
} from './static-ext-filtering-db.js';

import {
domainFromHostname,
entityFromDomain,
hostnameFromURI,
} from './uri-utils.js';

/******************************************************************************/

const cosmeticSurveyingMissCountMax =
Expand Down Expand Up @@ -1137,43 +1131,6 @@ FilterContainer.prototype.getFilterCount = function() {

/******************************************************************************/

FilterContainer.prototype.benchmark = async function() {
const requests = await µb.loadBenchmarkDataset();
if ( Array.isArray(requests) === false || requests.length === 0 ) {
console.info('No requests found to benchmark');
return;
}
console.info('Benchmarking cosmeticFilteringEngine.retrieveSpecificSelectors()...');
const details = {
tabId: undefined,
frameId: undefined,
hostname: '',
domain: '',
entity: '',
};
const options = {
noSpecificCosmeticFiltering: false,
noGenericCosmeticFiltering: false,
};
let count = 0;
const t0 = self.performance.now();
for ( let i = 0; i < requests.length; i++ ) {
const request = requests[i];
if ( request.cpt !== 'main_frame' ) { continue; }
count += 1;
details.hostname = hostnameFromURI(request.url);
details.domain = domainFromHostname(details.hostname);
details.entity = entityFromDomain(details.domain);
void this.retrieveSpecificSelectors(details, options);
}
const t1 = self.performance.now();
const dur = t1 - t0;
console.info(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`);
console.info(`\tAverage: ${(dur / count).toFixed(3)} ms per request`);
};

/******************************************************************************/

const cosmeticFilteringEngine = new FilterContainer();

export default cosmeticFilteringEngine;
Expand Down
Loading

0 comments on commit 8ef8c5a

Please sign in to comment.