416 lines
35 KiB
JavaScript
416 lines
35 KiB
JavaScript
"use strict";
|
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const Job_1 = require("./Job");
|
|
const Display_1 = require("./Display");
|
|
const util = require("./util");
|
|
const Worker_1 = require("./Worker");
|
|
const builtInConcurrency = require("./concurrency/builtInConcurrency");
|
|
const Queue_1 = require("./Queue");
|
|
const SystemMonitor_1 = require("./SystemMonitor");
|
|
const events_1 = require("events");
|
|
const debug = util.debugGenerator('Cluster');
|
|
const DEFAULT_OPTIONS = {
|
|
concurrency: 2, // CONTEXT
|
|
maxConcurrency: 1,
|
|
workerCreationDelay: 0,
|
|
puppeteerOptions: {
|
|
// headless: false, // just for testing...
|
|
},
|
|
perBrowserOptions: undefined,
|
|
monitor: false,
|
|
timeout: 30 * 1000,
|
|
retryLimit: 0,
|
|
retryDelay: 0,
|
|
skipDuplicateUrls: false,
|
|
sameDomainDelay: 0,
|
|
puppeteer: undefined,
|
|
};
|
|
const MONITORING_DISPLAY_INTERVAL = 500;
|
|
const CHECK_FOR_WORK_INTERVAL = 100;
|
|
const WORK_CALL_INTERVAL_LIMIT = 10;
|
|
class Cluster extends events_1.EventEmitter {
|
|
static launch(options) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
debug('Launching');
|
|
const cluster = new Cluster(options);
|
|
yield cluster.init();
|
|
return cluster;
|
|
});
|
|
}
|
|
constructor(options) {
|
|
super();
|
|
this.perBrowserOptions = null;
|
|
this.workers = [];
|
|
this.workersAvail = [];
|
|
this.workersBusy = [];
|
|
this.workersStarting = 0;
|
|
this.allTargetCount = 0;
|
|
this.jobQueue = new Queue_1.default();
|
|
this.errorCount = 0;
|
|
this.taskFunction = null;
|
|
this.idleResolvers = [];
|
|
this.waitForOneResolvers = [];
|
|
this.browser = null;
|
|
this.isClosed = false;
|
|
this.startTime = Date.now();
|
|
this.nextWorkerId = -1;
|
|
this.monitoringInterval = null;
|
|
this.display = null;
|
|
this.duplicateCheckUrls = new Set();
|
|
this.lastDomainAccesses = new Map();
|
|
this.systemMonitor = new SystemMonitor_1.default();
|
|
this.checkForWorkInterval = null;
|
|
this.nextWorkCall = 0;
|
|
this.workCallTimeout = null;
|
|
this.lastLaunchedWorkerTime = 0;
|
|
this.options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
|
|
if (this.options.monitor) {
|
|
this.monitoringInterval = setInterval(() => this.monitor(), MONITORING_DISPLAY_INTERVAL);
|
|
}
|
|
}
|
|
init() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
const browserOptions = this.options.puppeteerOptions;
|
|
let puppeteer = this.options.puppeteer;
|
|
if (this.options.puppeteer == null) { // check for null or undefined
|
|
puppeteer = require('puppeteer');
|
|
}
|
|
else {
|
|
debug('Using provided (custom) puppteer object.');
|
|
}
|
|
if (this.options.concurrency === Cluster.CONCURRENCY_PAGE) {
|
|
this.browser = new builtInConcurrency.Page(browserOptions, puppeteer);
|
|
}
|
|
else if (this.options.concurrency === Cluster.CONCURRENCY_CONTEXT) {
|
|
this.browser = new builtInConcurrency.Context(browserOptions, puppeteer);
|
|
}
|
|
else if (this.options.concurrency === Cluster.CONCURRENCY_BROWSER) {
|
|
this.browser = new builtInConcurrency.Browser(browserOptions, puppeteer);
|
|
}
|
|
else if (typeof this.options.concurrency === 'function') {
|
|
this.browser = new this.options.concurrency(browserOptions, puppeteer);
|
|
}
|
|
else {
|
|
throw new Error(`Unknown concurrency option: ${this.options.concurrency}`);
|
|
}
|
|
if (typeof this.options.maxConcurrency !== 'number') {
|
|
throw new Error('maxConcurrency must be of number type');
|
|
}
|
|
if (this.options.perBrowserOptions
|
|
&& this.options.perBrowserOptions.length !== this.options.maxConcurrency) {
|
|
throw new Error('perBrowserOptions length must equal maxConcurrency');
|
|
}
|
|
if (this.options.perBrowserOptions) {
|
|
this.perBrowserOptions = [...this.options.perBrowserOptions];
|
|
}
|
|
try {
|
|
yield this.browser.init();
|
|
}
|
|
catch (err) {
|
|
throw new Error(`Unable to launch browser, error message: ${err.message}`);
|
|
}
|
|
if (this.options.monitor) {
|
|
yield this.systemMonitor.init();
|
|
}
|
|
// needed in case resources are getting free (like CPU/memory) to check if
|
|
// can launch workers
|
|
this.checkForWorkInterval = setInterval(() => this.work(), CHECK_FOR_WORK_INTERVAL);
|
|
});
|
|
}
|
|
launchWorker() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
// signal, that we are starting a worker
|
|
this.workersStarting += 1;
|
|
this.nextWorkerId += 1;
|
|
this.lastLaunchedWorkerTime = Date.now();
|
|
let nextWorkerOption;
|
|
if (this.perBrowserOptions && this.perBrowserOptions.length > 0) {
|
|
nextWorkerOption = this.perBrowserOptions.shift();
|
|
}
|
|
const workerId = this.nextWorkerId;
|
|
let workerBrowserInstance;
|
|
try {
|
|
workerBrowserInstance = yield this.browser
|
|
.workerInstance(nextWorkerOption);
|
|
}
|
|
catch (err) {
|
|
throw new Error(`Unable to launch browser for worker, error message: ${err.message}`);
|
|
}
|
|
const worker = new Worker_1.default({
|
|
cluster: this,
|
|
args: [''], // this.options.args,
|
|
browser: workerBrowserInstance,
|
|
id: workerId,
|
|
});
|
|
this.workersStarting -= 1;
|
|
if (this.isClosed) {
|
|
// cluster was closed while we created a new worker (should rarely happen)
|
|
worker.close();
|
|
}
|
|
else {
|
|
this.workersAvail.push(worker);
|
|
this.workers.push(worker);
|
|
}
|
|
});
|
|
}
|
|
task(taskFunction) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
this.taskFunction = taskFunction;
|
|
});
|
|
}
|
|
// check for new work soon (wait if there will be put more data into the queue, first)
|
|
work() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
// make sure, we only call work once every WORK_CALL_INTERVAL_LIMIT (currently: 10ms)
|
|
if (this.workCallTimeout === null) {
|
|
const now = Date.now();
|
|
// calculate when the next work call should happen
|
|
this.nextWorkCall = Math.max(this.nextWorkCall + WORK_CALL_INTERVAL_LIMIT, now);
|
|
const timeUntilNextWorkCall = this.nextWorkCall - now;
|
|
this.workCallTimeout = setTimeout(() => {
|
|
this.workCallTimeout = null;
|
|
this.doWork();
|
|
}, timeUntilNextWorkCall);
|
|
}
|
|
});
|
|
}
|
|
doWork() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
if (this.jobQueue.size() === 0) { // no jobs available
|
|
if (this.workersBusy.length === 0) {
|
|
this.idleResolvers.forEach(resolve => resolve());
|
|
}
|
|
return;
|
|
}
|
|
if (this.workersAvail.length === 0) { // no workers available
|
|
if (this.allowedToStartWorker()) {
|
|
yield this.launchWorker();
|
|
this.work();
|
|
}
|
|
return;
|
|
}
|
|
const job = this.jobQueue.shift();
|
|
if (job === undefined) {
|
|
// skip, there are items in the queue but they are all delayed
|
|
return;
|
|
}
|
|
const url = job.getUrl();
|
|
const domain = job.getDomain();
|
|
// Check if URL was already crawled (on skipDuplicateUrls)
|
|
if (this.options.skipDuplicateUrls
|
|
&& url !== undefined && this.duplicateCheckUrls.has(url)) {
|
|
// already crawled, just ignore
|
|
debug(`Skipping duplicate URL: ${job.getUrl()}`);
|
|
this.work();
|
|
return;
|
|
}
|
|
// Check if the job needs to be delayed due to sameDomainDelay
|
|
if (this.options.sameDomainDelay !== 0 && domain !== undefined) {
|
|
const lastDomainAccess = this.lastDomainAccesses.get(domain);
|
|
if (lastDomainAccess !== undefined
|
|
&& lastDomainAccess + this.options.sameDomainDelay > Date.now()) {
|
|
this.jobQueue.push(job, {
|
|
delayUntil: lastDomainAccess + this.options.sameDomainDelay,
|
|
});
|
|
this.work();
|
|
return;
|
|
}
|
|
}
|
|
// Check are all positive, let's actually run the job
|
|
if (this.options.skipDuplicateUrls && url !== undefined) {
|
|
this.duplicateCheckUrls.add(url);
|
|
}
|
|
if (this.options.sameDomainDelay !== 0 && domain !== undefined) {
|
|
this.lastDomainAccesses.set(domain, Date.now());
|
|
}
|
|
const worker = this.workersAvail.shift();
|
|
this.workersBusy.push(worker);
|
|
if (this.workersAvail.length !== 0 || this.allowedToStartWorker()) {
|
|
// we can execute more work in parallel
|
|
this.work();
|
|
}
|
|
let jobFunction;
|
|
if (job.taskFunction !== undefined) {
|
|
jobFunction = job.taskFunction;
|
|
}
|
|
else if (this.taskFunction !== null) {
|
|
jobFunction = this.taskFunction;
|
|
}
|
|
else {
|
|
throw new Error('No task function defined!');
|
|
}
|
|
const result = yield worker.handle(jobFunction, job, this.options.timeout);
|
|
if (result.type === 'error') {
|
|
if (job.executeCallbacks) {
|
|
job.executeCallbacks.reject(result.error);
|
|
this.errorCount += 1;
|
|
}
|
|
else { // ignore retryLimits in case of executeCallbacks
|
|
job.addError(result.error);
|
|
const jobWillRetry = job.tries <= this.options.retryLimit;
|
|
this.emit('taskerror', result.error, job.data, jobWillRetry);
|
|
if (jobWillRetry) {
|
|
let delayUntil = undefined;
|
|
if (this.options.retryDelay !== 0) {
|
|
delayUntil = Date.now() + this.options.retryDelay;
|
|
}
|
|
this.jobQueue.push(job, {
|
|
delayUntil,
|
|
});
|
|
}
|
|
else {
|
|
this.errorCount += 1;
|
|
}
|
|
}
|
|
}
|
|
else if (result.type === 'success' && job.executeCallbacks) {
|
|
job.executeCallbacks.resolve(result.data);
|
|
}
|
|
this.waitForOneResolvers.forEach(resolve => resolve(job.data));
|
|
this.waitForOneResolvers = [];
|
|
// add worker to available workers again
|
|
const workerIndex = this.workersBusy.indexOf(worker);
|
|
this.workersBusy.splice(workerIndex, 1);
|
|
this.workersAvail.push(worker);
|
|
this.work();
|
|
});
|
|
}
|
|
allowedToStartWorker() {
|
|
const workerCount = this.workers.length + this.workersStarting;
|
|
return (
|
|
// option: maxConcurrency
|
|
(this.options.maxConcurrency === 0
|
|
|| workerCount < this.options.maxConcurrency)
|
|
// just allow worker creaton every few milliseconds
|
|
&& (this.options.workerCreationDelay === 0
|
|
|| this.lastLaunchedWorkerTime + this.options.workerCreationDelay < Date.now()));
|
|
}
|
|
// Type Guard for TypeScript
|
|
isTaskFunction(data) {
|
|
return (typeof data === 'function');
|
|
}
|
|
queueJob(data, taskFunction, callbacks) {
|
|
let realData;
|
|
let realFunction;
|
|
if (this.isTaskFunction(data)) {
|
|
realFunction = data;
|
|
}
|
|
else {
|
|
realData = data;
|
|
realFunction = taskFunction;
|
|
}
|
|
const job = new Job_1.default(realData, realFunction, callbacks);
|
|
this.allTargetCount += 1;
|
|
this.jobQueue.push(job);
|
|
this.emit('queue', realData, realFunction);
|
|
this.work();
|
|
}
|
|
queue(data, taskFunction) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
this.queueJob(data, taskFunction);
|
|
});
|
|
}
|
|
execute(data, taskFunction) {
|
|
return new Promise((resolve, reject) => {
|
|
const callbacks = { resolve, reject };
|
|
this.queueJob(data, taskFunction, callbacks);
|
|
});
|
|
}
|
|
idle() {
|
|
return new Promise(resolve => this.idleResolvers.push(resolve));
|
|
}
|
|
waitForOne() {
|
|
return new Promise(resolve => this.waitForOneResolvers.push(resolve));
|
|
}
|
|
close() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
this.isClosed = true;
|
|
clearInterval(this.checkForWorkInterval);
|
|
clearTimeout(this.workCallTimeout);
|
|
// close workers
|
|
yield Promise.all(this.workers.map(worker => worker.close()));
|
|
try {
|
|
yield this.browser.close();
|
|
}
|
|
catch (err) {
|
|
debug(`Error: Unable to close browser, message: ${err.message}`);
|
|
}
|
|
if (this.monitoringInterval) {
|
|
this.monitor();
|
|
clearInterval(this.monitoringInterval);
|
|
}
|
|
if (this.display) {
|
|
this.display.close();
|
|
}
|
|
this.systemMonitor.close();
|
|
debug('Closed');
|
|
});
|
|
}
|
|
monitor() {
|
|
if (!this.display) {
|
|
this.display = new Display_1.default();
|
|
}
|
|
const display = this.display;
|
|
const now = Date.now();
|
|
const timeDiff = now - this.startTime;
|
|
const doneTargets = this.allTargetCount - this.jobQueue.size() - this.workersBusy.length;
|
|
const donePercentage = this.allTargetCount === 0
|
|
? 1 : (doneTargets / this.allTargetCount);
|
|
const donePercStr = (100 * donePercentage).toFixed(2);
|
|
const errorPerc = doneTargets === 0 ?
|
|
'0.00' : (100 * this.errorCount / doneTargets).toFixed(2);
|
|
const timeRunning = util.formatDuration(timeDiff);
|
|
let timeRemainingMillis = -1;
|
|
if (donePercentage !== 0) {
|
|
timeRemainingMillis = ((timeDiff) / donePercentage) - timeDiff;
|
|
}
|
|
const timeRemining = util.formatDuration(timeRemainingMillis);
|
|
const cpuUsage = this.systemMonitor.getCpuUsage().toFixed(1);
|
|
const memoryUsage = this.systemMonitor.getMemoryUsage().toFixed(1);
|
|
const pagesPerSecond = doneTargets === 0 ?
|
|
'0' : (doneTargets * 1000 / timeDiff).toFixed(2);
|
|
display.log(`== Start: ${util.formatDateTime(this.startTime)}`);
|
|
display.log(`== Now: ${util.formatDateTime(now)} (running for ${timeRunning})`);
|
|
display.log(`== Progress: ${doneTargets} / ${this.allTargetCount} (${donePercStr}%)`
|
|
+ `, errors: ${this.errorCount} (${errorPerc}%)`);
|
|
display.log(`== Remaining: ${timeRemining} (@ ${pagesPerSecond} pages/second)`);
|
|
display.log(`== Sys. load: ${cpuUsage}% CPU / ${memoryUsage}% memory`);
|
|
display.log(`== Workers: ${this.workers.length + this.workersStarting}`);
|
|
this.workers.forEach((worker, i) => {
|
|
const isIdle = this.workersAvail.indexOf(worker) !== -1;
|
|
let workOrIdle;
|
|
let workerUrl = '';
|
|
if (isIdle) {
|
|
workOrIdle = 'IDLE';
|
|
}
|
|
else {
|
|
workOrIdle = 'WORK';
|
|
if (worker.activeTarget) {
|
|
workerUrl = worker.activeTarget.getUrl() || 'UNKNOWN TARGET';
|
|
}
|
|
else {
|
|
workerUrl = 'NO TARGET (should not be happening)';
|
|
}
|
|
}
|
|
display.log(` #${i} ${workOrIdle} ${workerUrl}`);
|
|
});
|
|
for (let i = 0; i < this.workersStarting; i += 1) {
|
|
display.log(` #${this.workers.length + i} STARTING...`);
|
|
}
|
|
display.resetCursor();
|
|
}
|
|
}
|
|
Cluster.CONCURRENCY_PAGE = 1; // shares cookies, etc.
|
|
Cluster.CONCURRENCY_CONTEXT = 2; // no cookie sharing (uses contexts)
|
|
Cluster.CONCURRENCY_BROWSER = 3; // no cookie sharing and individual processes (uses contexts)
|
|
exports.default = Cluster;
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ2x1c3Rlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9DbHVzdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7O0FBQ0EsK0JBQTZFO0FBQzdFLHVDQUFnQztBQUNoQywrQkFBK0I7QUFDL0IscUNBQThDO0FBRTlDLHVFQUF1RTtBQUd2RSxtQ0FBNEI7QUFDNUIsbURBQTRDO0FBQzVDLG1DQUFzQztBQUl0QyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBdUI3QyxNQUFNLGVBQWUsR0FBbUI7SUFDcEMsV0FBVyxFQUFFLENBQUMsRUFBRSxVQUFVO0lBQzFCLGNBQWMsRUFBRSxDQUFDO0lBQ2pCLG1CQUFtQixFQUFFLENBQUM7SUFDdEIsZ0JBQWdCLEVBQUU7SUFDZCwwQ0FBMEM7S0FDN0M7SUFDRCxpQkFBaUIsRUFBRSxTQUFTO0lBQzVCLE9BQU8sRUFBRSxLQUFLO0lBQ2QsT0FBTyxFQUFFLEVBQUUsR0FBRyxJQUFJO0lBQ2xCLFVBQVUsRUFBRSxDQUFDO0lBQ2IsVUFBVSxFQUFFLENBQUM7SUFDYixpQkFBaUIsRUFBRSxLQUFLO0lBQ3hCLGVBQWUsRUFBRSxDQUFDO0lBQ2xCLFNBQVMsRUFBRSxTQUFTO0NBQ3ZCLENBQUM7QUFjRixNQUFNLDJCQUEyQixHQUFHLEdBQUcsQ0FBQztBQUN4QyxNQUFNLHVCQUF1QixHQUFHLEdBQUcsQ0FBQztBQUNwQyxNQUFNLHdCQUF3QixHQUFHLEVBQUUsQ0FBQztBQUVwQyxNQUFxQixPQUF5QyxTQUFRLHFCQUFZO0lBb0N2RSxNQUFNLENBQU8sTUFBTSxDQUFDLE9BQStCOztZQUN0RCxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUM7WUFDbkIsTUFBTSxPQUFPLEdBQUcsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDckMsTUFBTSxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7WUFFckIsT0FBTyxPQUFPLENBQUM7UUFD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
|