2018-10-24 00:09:58 +08:00
|
|
|
'use strict';
|
|
|
|
|
2015-08-27 22:28:16 +08:00
|
|
|
var http = require('http');
|
|
|
|
var https = require('https');
|
2015-04-07 19:00:20 +08:00
|
|
|
var path = require('path');
|
|
|
|
var fs = require('fs');
|
|
|
|
var _ = require('underscore');
|
2017-03-25 00:28:23 +08:00
|
|
|
var semver = require('semver');
|
2018-01-11 18:57:11 +08:00
|
|
|
const setICUEnvVariable = require('./lib/cartodb/utils/icu_data_env_setter');
|
2017-03-25 00:28:23 +08:00
|
|
|
|
2017-03-28 00:27:55 +08:00
|
|
|
// jshint undef:false
|
2016-09-15 07:35:38 +08:00
|
|
|
var log = console.log.bind(console);
|
|
|
|
var logError = console.error.bind(console);
|
|
|
|
// jshint undef:true
|
|
|
|
|
2017-09-25 22:55:17 +08:00
|
|
|
var nodejsVersion = process.versions.node;
|
2019-03-27 02:14:39 +08:00
|
|
|
const { engines } = require('./package.json');
|
|
|
|
if (!semver.satisfies(nodejsVersion, engines.node)) {
|
|
|
|
logError(`Node version ${nodejsVersion} is not supported, please use Node.js ${engines.node}.`);
|
2017-09-25 22:55:17 +08:00
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:51:48 +08:00
|
|
|
// This function should be called before the require('yargs').
|
2018-01-10 18:13:49 +08:00
|
|
|
setICUEnvVariable();
|
|
|
|
|
2016-09-15 07:25:33 +08:00
|
|
|
var argv = require('yargs')
|
|
|
|
.usage('Usage: $0 <environment> [options]')
|
|
|
|
.help('h')
|
|
|
|
.example(
|
|
|
|
'$0 production -c /etc/sql-api/config.js',
|
|
|
|
'start server in production environment with /etc/sql-api/config.js as config file'
|
|
|
|
)
|
|
|
|
.alias('h', 'help')
|
|
|
|
.alias('c', 'config')
|
|
|
|
.nargs('c', 1)
|
|
|
|
.describe('c', 'Load configuration from path')
|
|
|
|
.argv;
|
|
|
|
|
|
|
|
var environmentArg = argv._[0] || process.env.NODE_ENV || 'development';
|
|
|
|
var configurationFile = path.resolve(argv.config || './config/environments/' + environmentArg + '.js');
|
|
|
|
if (!fs.existsSync(configurationFile)) {
|
2016-09-15 07:35:38 +08:00
|
|
|
logError('Configuration file "%s" does not exist', configurationFile);
|
2016-09-15 07:25:33 +08:00
|
|
|
process.exit(1);
|
2015-04-07 19:00:20 +08:00
|
|
|
}
|
2014-02-28 23:22:24 +08:00
|
|
|
|
2016-09-15 07:25:33 +08:00
|
|
|
global.environment = require(configurationFile);
|
|
|
|
var ENVIRONMENT = argv._[0] || process.env.NODE_ENV || global.environment.environment;
|
|
|
|
process.env.NODE_ENV = ENVIRONMENT;
|
|
|
|
|
2015-07-05 05:09:00 +08:00
|
|
|
var availableEnvironments = {
|
|
|
|
production: true,
|
|
|
|
staging: true,
|
|
|
|
development: true
|
|
|
|
};
|
2014-02-28 23:22:24 +08:00
|
|
|
|
2011-09-05 07:00:41 +08:00
|
|
|
// sanity check
|
2015-07-05 05:09:00 +08:00
|
|
|
if (!availableEnvironments[ENVIRONMENT]){
|
2016-05-05 18:18:22 +08:00
|
|
|
logError('node app.js [environment]');
|
|
|
|
logError('environments: %s', Object.keys(availableEnvironments).join(', '));
|
2011-10-07 22:38:28 +08:00
|
|
|
process.exit(1);
|
2011-09-05 07:00:41 +08:00
|
|
|
}
|
|
|
|
|
2015-07-05 05:09:00 +08:00
|
|
|
process.env.NODE_ENV = ENVIRONMENT;
|
2014-10-08 22:50:35 +08:00
|
|
|
if (global.environment.uv_threadpool_size) {
|
|
|
|
process.env.UV_THREADPOOL_SIZE = global.environment.uv_threadpool_size;
|
|
|
|
}
|
|
|
|
|
2015-08-28 19:54:56 +08:00
|
|
|
// set global HTTP and HTTPS agent default configurations
|
|
|
|
// ref https://nodejs.org/api/http.html#http_new_agent_options
|
|
|
|
var agentOptions = _.defaults(global.environment.httpAgent || {}, {
|
|
|
|
keepAlive: false,
|
|
|
|
keepAliveMsecs: 1000,
|
|
|
|
maxSockets: Infinity,
|
|
|
|
maxFreeSockets: 256
|
|
|
|
});
|
|
|
|
http.globalAgent = new http.Agent(agentOptions);
|
|
|
|
https.globalAgent = new https.Agent(agentOptions);
|
|
|
|
|
2016-09-15 02:15:38 +08:00
|
|
|
|
|
|
|
global.log4js = require('log4js');
|
|
|
|
var log4jsConfig = {
|
|
|
|
appenders: [],
|
|
|
|
replaceConsole: true
|
|
|
|
};
|
|
|
|
|
2014-03-29 01:05:18 +08:00
|
|
|
if ( global.environment.log_filename ) {
|
2016-09-15 02:15:38 +08:00
|
|
|
var logFilename = path.resolve(global.environment.log_filename);
|
|
|
|
var logDirectory = path.dirname(logFilename);
|
|
|
|
if (!fs.existsSync(logDirectory)) {
|
|
|
|
logError("Log filename directory does not exist: " + logDirectory);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
log("Logs will be written to " + logFilename);
|
|
|
|
log4jsConfig.appenders.push(
|
|
|
|
{ type: "file", absolute: true, filename: logFilename }
|
|
|
|
);
|
2014-03-29 01:05:18 +08:00
|
|
|
} else {
|
2016-09-15 02:15:38 +08:00
|
|
|
log4jsConfig.appenders.push(
|
|
|
|
{ type: "console", layout: { type:'basic' } }
|
|
|
|
);
|
2014-03-29 01:05:18 +08:00
|
|
|
}
|
|
|
|
|
2016-09-15 02:15:38 +08:00
|
|
|
global.log4js.configure(log4jsConfig);
|
2015-04-07 19:00:20 +08:00
|
|
|
global.logger = global.log4js.getLogger();
|
2014-02-18 22:12:08 +08:00
|
|
|
|
2013-12-18 00:35:12 +08:00
|
|
|
// Include cartodb_windshaft only _after_ the "global" variable is set
|
|
|
|
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/28
|
2015-07-05 05:09:00 +08:00
|
|
|
var cartodbWindshaft = require('./lib/cartodb/server');
|
|
|
|
var serverOptions = require('./lib/cartodb/server_options');
|
2013-01-30 00:36:50 +08:00
|
|
|
|
2015-07-05 05:09:00 +08:00
|
|
|
var server = cartodbWindshaft(serverOptions);
|
2014-10-15 04:12:35 +08:00
|
|
|
|
2013-01-30 00:36:50 +08:00
|
|
|
// Maximum number of connections for one process
|
|
|
|
// 128 is a good number if you have up to 1024 filedescriptors
|
|
|
|
// 4 is good if you have max 32 filedescriptors
|
|
|
|
// 1 is good if you have max 16 filedescriptors
|
2015-09-18 22:25:10 +08:00
|
|
|
var backlog = global.environment.maxConnections || 128;
|
2013-01-30 00:36:50 +08:00
|
|
|
|
2015-09-18 22:25:10 +08:00
|
|
|
var listener = server.listen(serverOptions.bind.port, serverOptions.bind.host, backlog);
|
2013-01-30 00:36:50 +08:00
|
|
|
|
2014-03-11 19:21:00 +08:00
|
|
|
var version = require("./package").version;
|
|
|
|
|
2015-09-17 08:04:10 +08:00
|
|
|
listener.on('listening', function() {
|
2017-02-10 17:03:06 +08:00
|
|
|
log("Using Node.js %s", process.version);
|
2016-09-15 07:25:33 +08:00
|
|
|
log('Using configuration file "%s"', configurationFile);
|
2016-05-05 18:18:22 +08:00
|
|
|
log(
|
2015-08-24 18:45:21 +08:00
|
|
|
"Windshaft tileserver %s started on %s:%s PID=%d (%s)",
|
2015-08-28 19:54:56 +08:00
|
|
|
version, serverOptions.bind.host, serverOptions.bind.port, process.pid, ENVIRONMENT
|
2015-08-24 18:45:21 +08:00
|
|
|
);
|
2012-10-09 23:09:40 +08:00
|
|
|
});
|
2013-01-30 18:29:44 +08:00
|
|
|
|
2018-11-16 00:28:18 +08:00
|
|
|
function getCPUUsage (oldUsage) {
|
|
|
|
let usage;
|
|
|
|
|
|
|
|
if (oldUsage && oldUsage._start) {
|
|
|
|
usage = Object.assign({}, process.cpuUsage(oldUsage._start.cpuUsage));
|
|
|
|
usage.time = Date.now() - oldUsage._start.time;
|
|
|
|
} else {
|
|
|
|
usage = Object.assign({}, process.cpuUsage());
|
|
|
|
usage.time = process.uptime() * 1000; // s to ms
|
|
|
|
}
|
|
|
|
|
|
|
|
usage.percent = (usage.system + usage.user) / (usage.time * 10);
|
|
|
|
|
|
|
|
Object.defineProperty(usage, '_start', {
|
|
|
|
value: {
|
|
|
|
cpuUsage: process.cpuUsage(),
|
|
|
|
time: Date.now()
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return usage;
|
|
|
|
}
|
|
|
|
|
|
|
|
let previousCPUUsage = getCPUUsage();
|
|
|
|
setInterval(function cpuUsageMetrics () {
|
|
|
|
const CPUUsage = getCPUUsage(previousCPUUsage);
|
|
|
|
|
|
|
|
Object.keys(CPUUsage).forEach(property => {
|
|
|
|
global.statsClient.gauge(`windshaft.cpu.${property}`, CPUUsage[property]);
|
|
|
|
});
|
|
|
|
|
|
|
|
previousCPUUsage = CPUUsage;
|
|
|
|
}, 5000);
|
|
|
|
|
2015-08-28 19:54:56 +08:00
|
|
|
setInterval(function() {
|
|
|
|
var memoryUsage = process.memoryUsage();
|
|
|
|
Object.keys(memoryUsage).forEach(function(k) {
|
|
|
|
global.statsClient.gauge('windshaft.memory.' + k, memoryUsage[k]);
|
|
|
|
});
|
|
|
|
}, 5000);
|
|
|
|
|
2014-03-29 01:05:18 +08:00
|
|
|
process.on('SIGHUP', function() {
|
2014-12-02 22:05:28 +08:00
|
|
|
global.log4js.clearAndShutdownAppenders(function() {
|
2016-09-15 02:15:38 +08:00
|
|
|
global.log4js.configure(log4jsConfig);
|
2015-04-07 19:00:20 +08:00
|
|
|
global.logger = global.log4js.getLogger();
|
2016-05-05 18:18:22 +08:00
|
|
|
log('Log files reloaded');
|
2014-12-02 22:05:28 +08:00
|
|
|
});
|
2013-03-18 23:37:31 +08:00
|
|
|
});
|
2014-02-18 22:12:08 +08:00
|
|
|
|
2017-03-29 21:56:30 +08:00
|
|
|
if (global.gc) {
|
|
|
|
var gcInterval = Number.isFinite(global.environment.gc_interval) ?
|
|
|
|
global.environment.gc_interval :
|
|
|
|
10000;
|
|
|
|
|
|
|
|
if (gcInterval > 0) {
|
|
|
|
setInterval(function gcForcedCycle() {
|
|
|
|
global.gc();
|
|
|
|
}, gcInterval);
|
|
|
|
}
|
|
|
|
}
|
2019-01-03 18:47:54 +08:00
|
|
|
|
|
|
|
const gcStats = require('gc-stats')();
|
|
|
|
|
|
|
|
gcStats.on('stats', function ({ pauseMS, gctype }) {
|
|
|
|
global.statsClient.timing('windshaft.gc', pauseMS);
|
|
|
|
global.statsClient.timing(`windshaft.gctype.${getGCTypeValue(gctype)}`, pauseMS);
|
|
|
|
});
|
|
|
|
|
|
|
|
function getGCTypeValue (type) {
|
|
|
|
// 1: Scavenge (minor GC)
|
|
|
|
// 2: Mark/Sweep/Compact (major GC)
|
|
|
|
// 4: Incremental marking
|
|
|
|
// 8: Weak/Phantom callback processing
|
|
|
|
// 15: All
|
|
|
|
let value;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 1:
|
|
|
|
value = 'Scavenge';
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
value = 'MarkSweepCompact';
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
value = 'IncrementalMarking';
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
value = 'ProcessWeakCallbacks';
|
|
|
|
break;
|
|
|
|
case 15:
|
|
|
|
value = 'All';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
value = 'Unkown';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
2019-03-27 01:47:57 +08:00
|
|
|
|
2019-03-27 01:57:49 +08:00
|
|
|
addHandlers(listener, global.logger, 45000);
|
2019-03-27 01:47:57 +08:00
|
|
|
|
|
|
|
function addHandlers(listener, logger, killTimeout) {
|
|
|
|
process.on('uncaughtException', exitProcess(listener, logger, killTimeout));
|
|
|
|
process.on('unhandledRejection', exitProcess(listener, logger, killTimeout));
|
2019-03-27 01:54:42 +08:00
|
|
|
process.on('ENOMEM', exitProcess(listener, logger, killTimeout));
|
2019-03-27 01:47:57 +08:00
|
|
|
process.on('SIGINT', exitProcess(listener, logger, killTimeout));
|
|
|
|
process.on('SIGTERM', exitProcess(listener, logger, killTimeout));
|
|
|
|
}
|
|
|
|
|
|
|
|
function exitProcess (listener, logger, killTimeout) {
|
|
|
|
return function exitProcessFn (signal) {
|
|
|
|
scheduleForcedExit(killTimeout, logger);
|
|
|
|
|
|
|
|
let code = 0;
|
|
|
|
|
|
|
|
if (!['SIGINT', 'SIGTERM'].includes(signal)) {
|
|
|
|
const err = signal instanceof Error ? signal : new Error(signal);
|
|
|
|
signal = undefined;
|
|
|
|
code = 1;
|
|
|
|
|
|
|
|
logger.fatal(err);
|
|
|
|
} else {
|
|
|
|
logger.info(`Process has received signal: ${signal}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info(`Process is going to exit with code: ${code}`);
|
2019-03-27 02:06:10 +08:00
|
|
|
listener.close(() => global.log4js.shutdown(() => process.exit(code)));
|
2019-03-27 01:47:57 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function scheduleForcedExit (killTimeout, logger) {
|
|
|
|
// Schedule exit if there is still ongoing work to deal with
|
|
|
|
const killTimer = setTimeout(() => {
|
|
|
|
logger.info('Process didn\'t close on time. Force exit');
|
|
|
|
process.exit(1);
|
|
|
|
}, killTimeout);
|
|
|
|
|
|
|
|
// Don't keep the process open just for this
|
|
|
|
killTimer.unref();
|
|
|
|
}
|