uBlock/platform/dig/snfe.js

239 lines
8.1 KiB
JavaScript

/*******************************************************************************
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
*/
/* eslint-disable-next-line no-redeclare */
/* globals process */
'use strict';
/******************************************************************************/
import { strict as assert } from 'assert';
import { createRequire } from 'module';
import { readFileSync, writeFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
import { enableWASM, StaticNetFilteringEngine } from './index.js';
/******************************************************************************/
const FLAGS = process.argv.slice(2);
const COMPARE = FLAGS.includes('compare');
const MAXCOST = FLAGS.includes('maxcost');
const MINCOST = FLAGS.includes('mincost');
const RECORD = FLAGS.includes('record');
const WASM = FLAGS.includes('wasm');
const NEED_RESULTS = COMPARE || MAXCOST || MINCOST || RECORD;
/******************************************************************************/
function nanoToMilli(bigint) {
return (Number(bigint) / 1000000).toFixed(2) + ' ms';
}
function nanoToMicro(bigint) {
return (Number(bigint) / 1000).toFixed(2) + ' µs';
}
async function read(path) {
return readFileSync(resolve(__dirname, path), 'utf8');
}
async function write(path, data) {
return writeFileSync(resolve(__dirname, path), data, 'utf8');
}
/******************************************************************************/
async function compare(results) {
let before;
try {
const raw = await read('data/snfe.json');
before = new Map(JSON.parse(raw));
} catch(ex) {
console.log(ex);
console.log('Nothing to compare');
return;
}
const after = new Map(results);
const diffs = [];
for ( let i = 0; i < results.length; i++ ) {
const a = before.get(i);
const b = after.get(i);
if ( a.r === b.r ) { continue; }
diffs.push([ i, { before: a, after: b } ]);
}
return diffs;
}
/******************************************************************************/
async function bench() {
if ( WASM ) {
try {
const result = await enableWASM();
if ( result === true ) {
console.log('WASM code paths enabled');
}
} catch(ex) {
console.log(ex);
}
}
// This maps puppeteer types to WebRequest types
const WEBREQUEST_OPTIONS = {
// Consider document requests as sub_document. This is because the request
// dataset does not contain sub_frame or main_frame but only 'document' and
// different blockers have different behaviours.
document: 'sub_frame',
stylesheet: 'stylesheet',
image: 'image',
media: 'media',
font: 'font',
script: 'script',
xhr: 'xmlhttprequest',
fetch: 'xmlhttprequest',
websocket: 'websocket',
ping: 'ping',
// other
other: 'other',
eventsource: 'other',
manifest: 'other',
texttrack: 'other',
};
const require = createRequire(import.meta.url); // jshint ignore:line
const requests = await require('./node_modules/scaling-palm-tree/requests.json');
const engine = await StaticNetFilteringEngine.create();
let start = process.hrtime.bigint();
await engine.useLists([
read('assets/ublock/badware.txt')
.then(raw => ({ name: 'badware', raw })),
read('assets/ublock/filters.txt')
.then(raw => ({ name: 'filters', raw })),
read('assets/ublock/filters-2020.txt')
.then(raw => ({ name: 'filters-2020', raw })),
read('assets/ublock/filters-2021.txt')
.then(raw => ({ name: 'filters-2021', raw })),
read('assets/ublock/privacy.txt')
.then(raw => ({ name: 'privacy', raw })),
read('assets/ublock/resource-abuse.txt')
.then(raw => ({ name: 'resource-abuse', raw })),
read('assets/ublock/unbreak.txt')
.then(raw => ({ name: 'unbreak.txt', raw })),
read('assets/thirdparties/easylist-downloads.adblockplus.org/easylist.txt')
.then(raw => ({ name: 'easylist', raw })),
read('assets/thirdparties/easylist-downloads.adblockplus.org/easyprivacy.txt')
.then(raw => ({ name: 'easyprivacy', raw })),
read('assets/thirdparties/pgl.yoyo.org/as/serverlist')
.then(raw => ({ name: 'PGL', raw })),
read('assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt')
.then(raw => ({ name: 'urlhaus', raw })),
]);
let stop = process.hrtime.bigint();
console.log(`Filter lists parsed-compiled-loaded in ${nanoToMilli(stop - start)}`);
const details = {
r: 0,
f: undefined,
type: '',
url: '',
originURL: '',
t: 0,
};
// Dry run to let JS engine optimize hot JS code paths
for ( let i = 0; i < requests.length; i++ ) {
const request = requests[i];
details.type = WEBREQUEST_OPTIONS[request.cpt];
details.url = request.url;
details.originURL = request.frameUrl;
void engine.matchRequest(details);
}
const results = [];
let notBlockedCount = 0;
let blockedCount = 0;
let unblockedCount = 0;
start = process.hrtime.bigint();
for ( let i = 0; i < requests.length; i++ ) {
const request = requests[i];
const reqstart = process.hrtime.bigint();
details.type = WEBREQUEST_OPTIONS[request.cpt];
details.url = request.url;
details.originURL = request.frameUrl;
const r = engine.matchRequest(details);
if ( r === 0 ) {
notBlockedCount += 1;
} else if ( r === 1 ) {
blockedCount += 1;
} else {
unblockedCount += 1;
}
if ( NEED_RESULTS !== true ) { continue; }
const reqstop = process.hrtime.bigint();
details.r = r;
details.f = r !== 0 ? engine.toLogData().raw : undefined;
details.t = Math.round(Number(reqstop - reqstart) / 10) / 100;
results.push([ i, Object.assign({}, details) ]);
}
stop = process.hrtime.bigint();
console.log(`Matched ${requests.length} requests in ${nanoToMilli(stop - start)}`);
console.log(`\tNot blocked: ${notBlockedCount} requests`);
console.log(`\tBlocked: ${blockedCount} requests`);
console.log(`\tUnblocked: ${unblockedCount} requests`);
console.log(`\tAverage: ${nanoToMicro((stop - start) / BigInt(requests.length))} per request`);
if ( RECORD ) {
write('data/snfe.json', JSON.stringify(results, null, 2));
}
if ( COMPARE ) {
const diffs = await compare(results);
if ( Array.isArray(diffs) ) {
write('data/snfe.diffs.json', JSON.stringify(diffs, null, 2));
}
console.log(`Compare: ${diffs.length} requests differ`);
}
if ( MAXCOST ) {
const costly = results.sort((a,b) => b[1].t - a[1].t).slice(0, 1000);
write('data/snfe.maxcost.json', JSON.stringify(costly, null, 2));
}
if ( MINCOST ) {
const costly = results.sort((a,b) => a[1].t - b[1].t).slice(0, 1000);
write('data/snfe.mincost.json', JSON.stringify(costly, null, 2));
}
StaticNetFilteringEngine.release();
}
bench();
/******************************************************************************/