/*
* Author: Vlad Seryakov vseryakov@gmail.com
* backendjs 2018
*/
const lib = require(__dirname + '/../lib');
const logger = require(__dirname + '/../logger');
const os = require('os');
const v8 = require('v8');
const cluster = require("cluster");
const perf_hooks = require("perf_hooks");
/**
* Return a worker by id or list of workers, useful only in primary process only, always returns an object.
* @param {string|number} [filter] a string or number then it is used as worker id, returns a worker or empty string.
* @param {object} [filter] every worker is checked against all its properties:
* - if null a property must be empty: null, "", undefined
* - if undefined then a property must not be empty
* - otherwise it must match by value
* @return {object[]} list of worker Prociess objects
* @memberof module:lib
* @method getWorkers
*/
lib.getWorkers = function(filter)
{
var workers = cluster.workers || "";
if (!filter) return Object.values(workers);
if (typeof filter == "string" || typeof filter == "number") {
return workers[filter] || "";
}
return Object.values(workers).filter((w) => {
for (const p in filter) {
if (filter[p] === null && w[p]) return 0;
if (filter[p] === undefined && !w[p]) return 0;
if (filter[p] && filter[p] != w[p]) return 0;
}
return 1;
});
}
/**
* Send a message to all workers
* @param {string|number|object} filter - see {@link module:lib.getWorkers}
* @example
* lib.notifyWorkers("worker:restart", { worker_type: null });
* @memberof module:lib
* @method notifyWorkers
*/
lib.notifyWorkers = function(msg, filter)
{
for (const w of this.getWorkers(filter)) {
w.send(msg);
}
}
/**
* Kill all workers
* @param {string|number|object} filter - see {@link module:lib.getWorkers}
* @memberof module:lib
* @method killWorkers
*/
lib.killWorkers = function(filter)
{
for (const w of this.getWorkers(filter)) {
try { process.kill(w.process.pid) } catch (e) {}
}
}
/**
* Return current Unix user id
* @return {int}
* @memberof module:lib
* @method getuid
*/
lib.getuid = function()
{
return typeof process.getuid == "function" ? process.getuid() : -1;
}
/**
* Return a list of local interfaces, default is all active IPv4 unless `IPv6`` property is set.
* Skips 169.254 unless `all` is set.
* @param {object} [options]
* @return {object[]}
* @memberof module:lib
* @method networkInterfaces
*/
lib.networkInterfaces = function(options)
{
var intf = os.networkInterfaces(), rc = [];
Object.keys(intf).forEach((x) => {
intf[x].forEach((y) => {
if (!y.address || y.internal) return;
if (y.family != 'IPv4' && !options?.IPv6) return;
if (y.address.startsWith("169.254") && !options?.all) return;
y.dev = x;
rc.push(y);
});
});
return rc;
}
/**
* Drop root privileges and switch to a regular user
* @memberof module:lib
* @method dropPrivileges
*/
lib.dropPrivileges = function(uid, gid)
{
logger.debug('dropPrivileges:', uid, gid);
if (lib.getuid() == 0) {
if (gid) {
if (lib.isNumeric(gid)) gid = lib.toNumber(gid);
try { process.setgid(gid); } catch (e) { logger.error('setgid:', gid, e); }
}
if (uid) {
if (lib.isNumeric(uid)) uid = lib.toNumber(uid);
try { process.setuid(uid); } catch (e) { logger.error('setuid:', uid, e); }
}
}
}
/**
* Convert an IP address into integer
* @memberof module:lib
* @method ip2int
*/
lib.ip2int = function(ip)
{
return ip.split('.').reduce((int, oct) => (int << 8) + parseInt(oct, 10), 0) >>> 0;
}
/**
* Convert an integer into IP address
* @memberof module:lib
* @method int2ip
*/
lib.int2ip = function(int)
{
return [(int >>> 24) & 0xFF, (int >>> 16) & 0xFF, (int >>> 8) & 0xFF, int & 0xFF].join('.');
}
/**
* Return true if the given IP address is within the given CIDR block
* @memberof module:lib
* @method inCidr
*/
lib.inCidr = function(ip, cidr)
{
const [range, bits = 32] = cidr.split('/');
const mask = ~(Math.pow(2, (32 - bits)) - 1);
return (this.ip2int(ip) & mask) === (this.ip2int(range) & mask);
};
/**
* Return first and last IP addresses for the CIDR block
* @memberof module:lib
* @method cidrRange
*/
lib.cidrRange = function(cidr)
{
const [range, bits = 32] = cidr.split('/');
const mask = ~(Math.pow(2, (32 - bits)) - 1);
return [this.int2ip(this.ip2int(range) & mask), this.int2ip(this.ip2int(range) | ~mask)];
}
/**
* Extract domain from the host name, takes all host parts except the first one, if toplevel is true return 2 levels only
* @param {string} host
* @param {boolean} [toplevel]
* @return {string}
* @memberof module:lib
* @method domainName
*/
lib.domainName = function(host, toplevel)
{
if (typeof host != "string" || !host) return "";
var name = this.split(host, '.');
return (toplevel ? name.slice(-2).join(".") : name.length > 2 ? name.slice(1).join('.') : host).toLowerCase();
}
lib._gc = { count: 0, time: 0 };
/**
* Return GC stats if enabled is true, starts if not running, if not enabled stops the observer
* @return {object}
* @memberof module:lib
* @method gcStats
*/
lib.gcStats = function(enabled)
{
if (!enabled) {
if (lib._gc.observer) lib._gc.observer.disconnect();
delete lib._gc.observer;
} else {
if (!lib._gc.observer) {
lib._gc.observer = new PerformanceObserver(list => {
for (const e of list.getEntries()) {
lib._gc.count++;
lib._gc.time += e.duration;
}
});
lib._gc.observer.observe({ type: 'gc' });
}
}
const { count, time } = lib._gc;
lib._gc.count = lib._gc.time = 0;
return { count, time };
}
lib._elu = perf_hooks.performance.eventLoopUtilization();
lib._proc_cpu = { usage: process.cpuUsage(), time: process.uptime() * 1000 };
lib._host_cpu = cpuStats();
function cpuStats()
{
const cpus = os.cpus();
var idle = 0, total = 0;
for (const cpu of cpus) {
idle += cpu.times.idle;
for (const p in cpu.times) total += cpu.times[p];
}
return [idle/cpus.length, total/cpus.length];
}
/**
* Return CPU process stats in an object
* @return {object}
* @memberof module:lib
* @method cpuStats
*/
lib.cpuStats = function()
{
var now = Date.now();
var elu = perf_hooks.performance.eventLoopUtilization(this._elu);
this._elu = perf_hooks.performance.eventLoopUtilization();
var usage = process.cpuUsage(this._proc_cpu.usage);
var pcpu = (usage.system/1000 + usage.user/1000) / (now - this._proc_cpu.time) * 100;
this._proc_cpu.usage = process.cpuUsage();
this._proc_cpu.time = now;
var stats = cpuStats();
var cpu = lib.toNumber(100 - (stats[0] - this._host_cpu[0]) / (stats[1] - this._host_cpu[1]) * 100, { min: 0, max: 100 });
this._host_cpu = stats;
return {
timestamp: now,
pid: process.pid,
eventloop_util: lib.toNumber(elu.utilization*100, { digits: 2 }),
proc_cpu_util: lib.toNumber(pcpu, { digits: 2 }),
host_cpu_util: lib.toNumber(cpu, { digits: 2 }),
};
}
/**
* @return {object}
* @memberof module:lib
* @method memoryStats
*/
lib.memoryStats = function()
{
return {
proc_mem_util: lib.toNumber(process.memoryUsage.rss() / os.totalmem() * 100, { digits: 2 }),
proc_mem_rss: process.memoryUsage.rss(),
host_mem_used: os.totalmem() - os.freemem(),
host_mem_util: lib.toNumber((os.totalmem() - os.freemem())/os.totalmem()*100, { digits: 2 }),
};
}
/**
* @return {object}
* @memberof module:lib
* @method heapStats
*/
lib.heapStats = function()
{
var heap = v8.getHeapStatistics();
var nspace, ospace;
v8.getHeapSpaceStatistics().forEach((x) => {
if (x.space_name == 'new_space') nspace = x; else
if (x.space_name == 'old_space') ospace = x;
});
return {
proc_heap_total: heap.total_heap_size,
proc_heap_used: heap.used_heap_size,
proc_heap_malloc: heap.malloced_memory,
proc_heap_external: heap.external_memory,
proc_heap_native: heap.number_of_native_contexts,
proc_heap_detached: heap.number_of_detached_contexts,
proc_heap_new_space: nspace?.space_used_size,
proc_heap_old_space: ospace?.space_used_size,
};
}
/**
* Return network stats for the instance, if not netdev is given it uses eth0 (Linux)
* @param {string} netdev
* @param {function} [callback] - if given pass stats via callback
* @return {object}
* @memberof module:lib
* @method networkStats
*/
lib.networkStats = function(netdev, callback)
{
var stats = {};
if (os.type() == "Linux") {
/*
* Inter-| Receive | Transmit
* face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
*/
function _parse(data) {
var eth = data.map((x) => (lib.split(x," "))).filter((x) => (x[0] == netdev + ":")).pop();
if (!eth) return;
var now = eth.now = Date.now();
stats.net_rx_bytes = lib.toNumber(eth[1]) - (lib._eth?.net_rx_bytes ?? 0);
stats.net_rx_packets = lib.toNumber(eth[2]) - (lib._eth?.net_rx_packets ?? 0);
stats.net_rx_errors = lib.toNumber(eth[3]) - (lib._eth?.net_rx_errors ?? 0);
stats.net_rx_dropped = lib.toNumber(eth[4]) - (lib._eth?.net_rx_dropped ?? 0);
stats.net_tx_bytes = lib.toNumber(eth[9]) - (lib._eth?.net_tx_bytes ?? 0);
stats.net_tx_packets = lib.toNumber(eth[10]) - (lib._eth?.net_tx_packets ?? 0);
stats.net_tx_errors = lib.toNumber(eth[11]) - (lib._eth?.net_tx_errors ?? 0);
stats.net_tx_dropped = lib.toNumber(eth[12]) - (lib._eth?.net_tx_dropped ?? 0);
if (lib._eth) {
var t = (now - lib._eth.now)/1000;
stats.net_rx_rate = lib.toNumber((stats.net_rx_bytes - lib.toNumber(lib._eth[1])) / t, { digits: 2 });
stats.net_tx_rate = lib.toNumber((stats.net_tx_bytes - lib.toNumber(lib._eth[9])) / t, { digits: 2 });
}
lib._eth = eth;
}
netdev = netdev || "eth0";
if (typeof callback == "function") {
return this.readFile("/proc/net/dev", { list: "\n" }, (err, data) => {
_parse(data);
callback(err, stats);
});
}
_parse(this.readFileSync("/proc/net/dev", { list: "\n" }));
} else {
if (typeof callback == "function") {
return callback(null, stats);
}
}
return stats;
}