UNPKG

yunkong2.js-controller

Version:

Updated by reinstall.js on 2018-06-11T15:19:56.688Z

1,240 lines (1,162 loc) 124 kB
/** * application.controller * * Controls Adapter-Processes * * Copyright 2013-2018 bluefox <dogafox@gmail.com>, * 2014 hobbyquaker <hq@ccu.io> * */ 'use strict'; const schedule = require('node-schedule'); const os = require('os'); const fs = require('fs'); const cp = require('child_process'); const ioPackage = require(__dirname + '/io-package.json'); const tools = require(__dirname + '/lib/tools'); const version = ioPackage.common.version; const pidusage = require('pidusage'); let adapterDir = __dirname.replace(/\\/g, '/'); let zipFiles; /* Use require('loadavg-windows') to enjoy os.loadavg() on Windows OS. Currently Node.js on Windows platform do not implements os.loadavg() functionality - it returns [0,0,0] Expect first results after 1 min from application start (before 1 min runtime it will return [0,0,0]) Requiring it on other operating systems have NO influence.*/ if (os.platform() === 'win32') { require('loadavg-windows'); } // Change version in io-package.json and start grunt task to modify the version const title = tools.appName + '.js-controller'; process.title = title; let Objects; let States; let semver; let logger; let isDaemon = false; let callbackId = 1; let callbacks = {}; let hostname = tools.getHostName(); let logList = []; let detectIpsCount = 0; let disconnectTimeout = null; let connected = null; // not false, because want to detect first connection let ipArr = []; let lastCalculationOfIps = null; let lastDiskSizeCheck = 0; const DESIRED_TERMINATION = 11; const errorCodes = [ 'OK', // 0 '', // 1 'Adapter has invalid config or no config found', // 2 'Adapter disabled or invalid config', // 3 'invalid config: no _id found', // 4 'invalid config', // 5 'uncaught exception', // 6 'Adapter already running', // 7 'node.js: Cannot find module', // 8 '', // 9 'Cannot find start file of adapter', // 10 'Desired termination' // 11 - DESIRED_TERMINATION ]; let procs = {}; let subscribe = {}; let states = null; let objects = null; let storeTimer = null; let isStopping = null; let allInstancesStopped = true; let stopTimeout = 10000; let uncaughtExceptionCount = 0; let installQueue = []; let started = false; let inputCount = 0; let outputCount = 0; let mhService = null; // multihost service let uptimeStart = Date.now(); const config = getConfig(); function getConfig() { const configFile = tools.getConfigFileName(); if (!fs.existsSync(configFile)) { if (process.argv.indexOf('start') !== -1) { isDaemon = true; logger = require(__dirname + '/lib/logger')('info', [tools.appName], true); } else { logger = require(__dirname + '/lib/logger')('info', [tools.appName]); } logger.error('host.' + hostname + ' conf/' + tools.appName + '.json missing - call node ' + tools.appName + '.js setup'); process.exit(1); return null; } else { let _config = JSON.parse(fs.readFileSync(configFile)); if (!_config.states) _config.states = {type: 'file'}; if (!_config.objects) _config.objects = {type: 'file'}; if (!_config.system) _config.system = {}; return _config; } } function _startMultihost(_config, secret) { const MHService = require(__dirname + '/lib/multihostServer.js'); const cpus = os.cpus(); mhService = new MHService(hostname, logger, _config, { node: process.version, arch: os.arch(), model: cpus && cpus[0] && cpus[0].model ? cpus[0].model : 'unknown', cpus: cpus ? cpus.length : 1, mem: os.totalmem(), ostype: os.type() }, getIPs(), secret); } function startMultihost(__config) { let _config = __config || getConfig(); if (_config.multihostService && _config.multihostService.enabled) { if (mhService) { try { mhService.close(function () { mhService = null; setImmediate(function () { startMultihost(_config); }); }); return; } catch (e) { logger.warn('Cannot stop multihost: ' + e); } } if ((!_config.objects.host || _config.objects.host === '127.0.0.1' || _config.objects.host === 'localhost') && _config.objects.type === 'file') { logger.warn('Host on this system is not possible, because IP address is for objects is ' + _config.objects.host); } else if ((_config.states.host || _config.states.host === '127.0.0.1' || _config.states.host === 'localhost') && _config.states.type === 'file') { logger.warn('Host on this system is not possible, because IP address is for states is ' + _config.states.host); } if (_config.multihostService.secure) { objects.getObject('system.config', function (err, obj) { if (obj && obj.native && obj.native.secret) { tools.decryptPhrase(obj.native.secret, _config.multihostService.password, function (secret) { _startMultihost(_config, secret); }); } else { logger.error('Cannot start multihost: no system.config found') } }); } else { _startMultihost(_config, false); } return true; } else if (mhService) { try { mhService.close(); mhService = null; } catch (e) { logger.warn('Cannot stop multihost: ' + e); } return false; } } // get the list of IP addresses of this host function getIPs() { if (!lastCalculationOfIps || Date.now() - lastCalculationOfIps > 10000) { const ifaces = os.networkInterfaces(); lastCalculationOfIps = Date.now(); ipArr = []; for (let dev in ifaces) { if (!ifaces.hasOwnProperty(dev)) continue; /*jshint loopfunc:true */ ifaces[dev].forEach(function (details) { //noinspection JSUnresolvedVariable if (!details.internal) ipArr.push(details.address); }); } } return ipArr; } // subscribe or unsubscribe loggers function logRedirect(isActive, id) { if (isActive) { if (logList.indexOf(id) === -1) logList.push(id); } else { const pos = logList.indexOf(id); if (pos !== -1) logList.splice(pos, 1); } } function createStates() { return new States({ namespace: 'host.' + hostname, connection: config.states, logger: logger, hostname: hostname, change: function (id, state) { inputCount++; if (!id) { logger.error('host.' + hostname + ' change event with no ID: ' + JSON.stringify(state)); return; } // If some log transporter activated or deactivated if (id.match(/.logging$/)) { logRedirect(state ? state.val : false, id.substring(0, id.length - '.logging'.length)); } else // If this is messagebox if (id === 'messagebox.system.host.' + hostname) { // Read it from fifo list states.delMessage('system.host.' + hostname, state._id); let obj = state; if (obj) { // If callback stored for this request if (obj.callback && obj.callback.ack && obj.callback.id && callbacks && callbacks['_' + obj.callback.id]) { // Call callback function if (callbacks['_' + obj.callback.id].cb) { callbacks['_' + obj.callback.id].cb(obj.message); delete callbacks['_' + obj.callback.id]; } // delete too old callbacks IDs let now = (new Date()).getTime(); for (let _id in callbacks) { if (!callbacks.hasOwnProperty(_id)) continue; if (now - callbacks[_id].time > 3600000) delete callbacks[_id]; } } else { processMessage(obj); } } } else // If this NAME.0.info.connection if (id.match(/^[^.]+\.\d+\.info\.connection$/)) { if (state && !state.val) { tools.setQualityForInstance(objects, states, id.substring(0, id.length - /* '.info.connection'.length*/ 16), 0x42) .then(() => { logger.debug('host.' + hostname + ' set all states quality to 0x42 (device not connected'); }).catch(e => { logger.error('host.' + hostname + ' cannot set all states quality: ' + e); }); } } else // If this system.adapter.NAME.0.alive if (id.match(/^system.adapter.[^.]+\.\d+\.alive$/)) { if (state && !state.ack) { let enabled = state.val; setImmediate(function () { objects.getObject(id.substring(0, id.length - 6/*'.alive'.length*/), function (err, obj) { if (err) logger.error('Cannot read object: ' + err); if (obj && obj.common) { // IF adapter enabled => disable it if ((obj.common.enabled && !enabled) || (!obj.common.enabled && enabled)) { obj.common.enabled = !!enabled; logger.warn('host.' + hostname + ' instance "' + obj._id + '" ' + (obj.common.enabled ? 'enabled' : 'disabled')); setImmediate(function () { obj.from = 'system.host.' + hostname; obj.ts = Date.now(); objects.setObject(obj._id, obj); }); } } }); }); } else if (state && state.ack && !state.val) { id = id.substring(0, id.length - /*.alive*/ 6); if (procs[id] && procs[id].config.common.host === hostname && procs[id].config.common.mode === 'daemon') { tools.setQualityForInstance(objects, states, id.substring(15 /*'system.adapter.'.length*/), 0x12) .then(() => { logger.debug('host.' + hostname + ' set all states quality to 0x12 (instance not connected'); }).catch(e => { logger.error('host.' + hostname + ' cannot set all states quality: ' + e); }); } } } else if (subscribe[id]) { for (let i = 0; i < subscribe[id].length; i++) { // wake up adapter if (procs[subscribe[id][i]]) { console.log('Wake up ' + id + ' ' + JSON.stringify(state)); startInstance(subscribe[id][i], true); } else { logger.warn('host.' + hostname + ' controller Adapter subscribed on ' + id + ' does not exist!'); } } } /* it is not used because of code before else // Monitor activity of the adapter and restart it if stopped if (!isStopping && id.substring(id.length - '.alive'.length) === '.alive') { let adapter = id.substring(0, id.length - '.alive'.length); if (procs[adapter] && !procs[adapter].stopping && !procs[adapter].process && procs[adapter].config && procs[adapter].config.common.enabled && procs[adapter].config.common.mode === 'daemon') { startInstance(adapter, false); } } */ }, connected: function () { if (states.clearAllLogs) states.clearAllLogs(); if (states.clearAllMessages) states.clearAllMessages(); } }); } // create "objects" object function createObjects() { return new Objects({ namespace: 'host.' + hostname, connection: config.objects, logger: logger, hostname: hostname, connected: function (type) { // stop disconnect timeout if (disconnectTimeout) { clearTimeout(disconnectTimeout); disconnectTimeout = null; } if (!connected) { logger.info('host.' + hostname + ' ' + type + ' connected'); if (connected === null) { connected = true; if (!isStopping) { // Do not start if we still stopping the instances checkHost(type, () => { startMultihost(config); setMeta(); started = true; getInstances(); startAliveInterval(); initMessageQueue(); }); } } else { connected = true; started = true; // Do not start if we still stopping the instances if (!isStopping) { getInstances(); startAliveInterval(); initMessageQueue(); } } } }, disconnected: function (/*error*/) { if (disconnectTimeout) clearTimeout(disconnectTimeout); disconnectTimeout = setTimeout(function () { connected = false; disconnectTimeout = null; logger.warn('host.' + hostname + ' Slave controller detected disconnection. Stop all instances.'); stopInstances(true, function () { // if during stopping the DB has connection again if (connected && !isStopping) { getInstances(); startAliveInterval(); initMessageQueue(); } }); }, config.objects.connectTimeout || 2000); }, change: function (id, obj) { if (!started || !id.match(/^system\.adapter\.[a-zA-Z0-9-_]+\.[0-9]+$/)) return; logger.info('host.' + hostname + ' object change ' + id); try{ if (procs[id]) { // known adapter if (!obj) { procs[id].config.common.enabled = false; procs[id].config.common.host = null; procs[id].config.deleted = true; logger.info('host.' + hostname + ' object deleted ' + id); } else { if (procs[id].config.common.enabled && !obj.common.enabled) logger.info('host.' + hostname + ' "' + id + '" disabled'); if (!procs[id].config.common.enabled && obj.common.enabled) logger.info('host.' + hostname + ' "' + id + '" enabled'); procs[id].config = obj; } if (procs[id].process || procs[id].config.common.mode === 'schedule' || procs[id].config.common.mode === 'subscribe') { stopInstance(id, function () { let _ipArr = getIPs(); if (_ipArr.indexOf(procs[id].config.common.host) !== -1 || procs[id].config.common.host === hostname) { if (procs[id].config.common.enabled && (!procs[id].config.common.webExtension || !procs[id].config.native.webInstance)) { if (procs[id].restartTimer) clearTimeout(procs[id].restartTimer); procs[id].restartTimer = setTimeout(function (_id) { startInstance(_id); }, 2500, id); } } else { delete procs[id]; } }); } else { let __ipArr = getIPs(); if (procs[id].config && (__ipArr.indexOf(procs[id].config.common.host) !== -1 || procs[id].config.common.host === hostname)) { if (procs[id].config.common.enabled && (!procs[id].config.common.webExtension || !procs[id].config.native.webInstance)) { startInstance(id); } } else { delete procs[id]; } } } else if (obj && obj.common) { let _ipArr = getIPs(); // new adapter if (_ipArr.indexOf(obj.common.host) !== -1 || obj.common.host === hostname) { procs[id] = {config: obj}; if (procs[id].config.common.enabled && (!procs[id].config.common.webExtension || !procs[id].config.native.webInstance)) { startInstance(id); } } } } catch (err) { logger.error('cannot process: ' + id); } } }); } function startAliveInterval() { config.system = config.system || {}; config.system.statisticsInterval = parseInt(config.system.statisticsInterval, 10) || 15000; config.system.checkDiskInterval = (config.system.checkDiskInterval !== 0) ? parseInt(config.system.checkDiskInterval, 10) || 300000 : 0; reportStatus(); setInterval(reportStatus, config.system.statisticsInterval); } function reportStatus() { let id = 'system.host.' + hostname; outputCount += 10; states.setState(id + '.alive', {val: true, ack: true, expire: Math.floor(config.system.statisticsInterval / 1000) + 10, from: id}); // provide infos about current process // pidusage([pid,pid,...], function (err, stats) { // => { // cpu: 10.0, // percentage (from 0 to 100*vcore) // memory: 357306368, // bytes // ppid: 312, // PPID // pid: 727, // PID // ctime: 867000, // ms user + system time // elapsed: 6650000, // ms since the start of the process // timestamp: 864000000 // ms since epoch // } pidusage(process.pid, (err, stats) => { // controller.s might be stopped, but this is still running if (!err && states && states.setState && stats) { states.setState(id + '.cpu', {ack: true, from: id, val: parseFloat(stats.cpu).toFixed(2)}); states.setState(id + '.cputime', {ack: true, from: id, val: stats.ctime / 1000}); outputCount+=2; } }); let mem = process.memoryUsage(); states.setState(id + '.memRss', {val: Math.round(mem.rss / 10485.76/* 1MB / 100 */) / 100, ack: true, from: id}); states.setState(id + '.memHeapTotal', {val: Math.round(mem.heapTotal / 10485.76/* 1MB / 100 */) / 100, ack: true, from: id}); states.setState(id + '.memHeapUsed', {val: Math.round(mem.heapUsed / 10485.76/* 1MB / 100 */) / 100, ack: true, from: id}); // provide machine infos states.setState(id + '.load', {val: Math.round(os.loadavg()[0] * 100) / 100, ack: true, from: id}); //require('loadavg-windows') states.setState(id + '.uptime', {val: Math.round(process.uptime()), ack: true, from: id}); states.setState(id + '.mem', {val: Math.round(1000 * os.freemem() / os.totalmem()) / 10, ack: true, from: id}); states.setState(id + '.freemem', {val: Math.round(os.freemem() / 1048576/* 1MB */), ack: true, from: id}); if (fs.existsSync('/proc/meminfo')) { try { let text = fs.readFileSync('/proc/meminfo', 'utf8'); let m = text && text.match(/MemAvailable:\s*(\d+)/); if (m && m[1]) { states.setState(id + '.memAvailable', {val: Math.round(parseInt(m[1], 10) * 0.001024), ack: true, from: id}); outputCount++; } } catch (err) { logger.error('Cannot read /proc/meminfo: ' + err); } } if (config.system.checkDiskInterval && Date.now() - lastDiskSizeCheck >= config.system.checkDiskInterval) { lastDiskSizeCheck = Date.now(); tools.getDiskInfo(os.platform(), (err, info) => { if (err) { logger.error('Cannot read disk size: ' + err); } try { if (info) { states.setState(id + '.diskSize', {val: Math.round((info['Disk size'] || 0) / (1024 * 1024)), ack: true, from: id}); states.setState(id + '.diskFree', {val: Math.round((info['Disk free'] || 0) / (1024 * 1024)), ack: true, from: id}); outputCount+=2; } } catch (e) { logger.error('Cannot read disk information: ' + e); } }); } // some statistics states.setState(id + '.inputCount', {val: inputCount, ack: true, from: id}); states.setState(id + '.outputCount', {val: outputCount, ack: true, from: id}); inputCount = 0; outputCount = 0; } function changeHost(objs, oldHostname, newHostname, callback) { if (!objs || !objs.length) { if (callback) callback(); } else { let row = objs.shift(); if (row && row.value && row.value.common && row.value.common.host === oldHostname) { let obj = row.value; obj.common.host = newHostname; logger.info('Reassign instance ' + obj._id.substring('system.adapter.'.length) + ' from ' + oldHostname + ' to ' + newHostname); obj.from = 'system.host.' + tools.getHostName(); obj.ts = Date.now(); objects.setObject(obj._id, obj, function (/* err */) { setImmediate(function () { changeHost(objs, oldHostname, newHostname, callback); }); }); } else { setImmediate(function () { changeHost(objs, oldHostname, newHostname, callback); }); } } } function cleanAutoSubscribe(instance, autoInstance, callback) { states.getState(autoInstance + '.subscribes', (err, state) => { if (!state || !state.val) { if (typeof callback === 'function') { setImmediate(function () { callback(); }); } return; } let subs; try { subs = JSON.parse(state.val) } catch (e) { logger.error('Cannot parse subscribes: ' + state.val); if (typeof callback === 'function') { setImmediate(function () { callback(); }); } return; } let modified = false; // look for all subscribes from this instance for (let pattern in subs) { if (!subs.hasOwnProperty(pattern)) continue; for (let id in subs[pattern]) { if (subs[pattern].hasOwnProperty(id) && id === instance) { modified = true; delete subs[pattern][id]; } } let found = false; for (let f in subs[pattern]) { if (subs[pattern].hasOwnProperty(f)) { found = true; break; } } if (!found) { modified = true; delete subs[pattern]; } } if (modified) { outputCount++; states.setState(autoInstance + '.subscribes', subs, () => (typeof callback === 'function') && callback()); } else if (typeof callback === 'function') { setImmediate(() => callback()); } }); } function cleanAutoSubscribes(instance, callback) { // instance = 'system.adapter.name.0' instance = instance.substring(15); // get name.0 // read all instances objects.getObjectView('system', 'instance', {startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, res) => { let count = 0; if (res && res.rows) { for (let c = res.rows.length - 1; c >= 0; c--) { // remove this instance from autoSubscribe if (res.rows[c].value.common.subscribable) { count++; cleanAutoSubscribe(instance, res.rows[c].id, () => { if (!--count && callback) callback(); }); } } } if (!count && callback) callback(); }); } function delObjects(objs, callback) { if (!objs || !objs.length) { if (callback) callback(); } else { let row = objs.shift(); if (row && row.id) { logger.info('Delete state "' + row.id + '"'); if (row.value.type === 'state') { states.delState(row.id, function (/* err */) { objects.delObject(row.id, function (/* err */) { setImmediate(function () { delObjects(objs, callback); }); }); }); } else { objects.delObject(row.id, function (/* err */) { setImmediate(function () { delObjects(objs, callback); }); }); } } else { setImmediate(function () { delObjects(objs, callback); }); } } } /** * try to check host in objects * <p> * This function tries to find all hosts in the objects and if * only one host found and it is not actual host, change the * host name to new one. * <p> * * @return none */ function checkHost(type, callback) { if (type === 'InMemoryDB') { objects.getObjectView('system', 'host', {}, function (_err, doc) { if (!_err && doc && doc.rows && doc.rows.length === 1 && doc.rows[0].value.common.name !== hostname) { let oldHostname = doc.rows[0].value.common.name; let oldId = doc.rows[0].value._id; // find out all instances and rewrite it to actual hostname objects.getObjectView('system', 'instance', {}, function (err, doc) { if (err && err.status_code === 404) { if (callback) callback(); } else if (doc.rows.length === 0) { logger.info('host.' + hostname + ' no instances found'); // no instances found if (callback) callback(); } else { // reassign all instances changeHost(doc.rows, oldHostname, hostname, function () { logger.info('Delete host ' + oldId); // delete host object objects.delObject(oldId, function () { // delete all hosts states objects.getObjectView('system', 'state', {startkey: 'system.host.' + oldHostname + '.', endkey: 'system.host.' + oldHostname + '.\u9999', include_docs: true}, function (_err, doc) { delObjects(doc.rows, function () { if (callback) callback(); }); }); }); }); } }); } else if (callback) { callback(); } }); } else { if (callback) callback(); } } // collect short diag information function collectDiagInfo(type, callback) { if (type !== 'extended' && type !== 'normal' && type !== 'no-city') { callback && callback(null); } else { objects.getObject('system.config', function (err, systemConfig) { objects.getObject('system.meta.uuid', function (err, obj) { // create uuid if (err || !obj) { obj = {native: {uuid: 'not found'}}; } objects.getObjectView('system', 'host', {}, function (_err, doc) { // we need to show city and country at the beginning, so include it now and delete it later if not allowed. let diag = { uuid: obj.native.uuid, language: systemConfig.common.language, country: '', city: '', hosts: [], node: process.version, arch: os.arch(), adapters: {} }; if (type === 'extended' || type === 'no-city') { diag.country = systemConfig.common.country; let cpus = os.cpus(); diag.model = cpus && cpus[0] && cpus[0].model ? cpus[0].model : 'unknown'; diag.cpus = cpus ? cpus.length : 1; diag.mem = os.totalmem(); diag.ostype = os.type(); delete diag.city; } if (type === 'extended') { diag.city = systemConfig.common.city; } else if (type === 'normal') { delete diag.city; delete diag.country; } if (!_err && doc) { if (doc && doc.rows.length) { if (!semver) semver = require('semver'); doc.rows.sort(function (a, b) { try { return semver.lt((a && a.value && a.value.common) ? a.value.common.installedVersion : '0.0.0', (b && b.value && b.value.common) ? b.value.common.installedVersion : '0.0.0'); } catch (e) { logger.error('host.' + hostname + ' Invalid versions: ' + ((a && a.value && a.value.common) ? a.value.common.installedVersion : '0.0.0') + '[' + ((a && a.value && a.value.common) ? a.value.common.name : 'unknown') + '] or ' + ((b && b.value && b.value.common) ? b.value.common.installedVersion : '0.0.0') + '[' + ((b && b.value && b.value.common) ? b.value.common.name : 'unknown') + ']'); return 0; } }); // Read installed versions of all hosts for (let i = 0; i < doc.rows.length; i++) { diag.hosts.push({ version: doc.rows[i].value.common.installedVersion, platform: doc.rows[i].value.common.platform, type: doc.rows[i].value.native.os.platform }); } } } objects.getObjectView('system', 'adapter', {}, function (__err, doc) { let visFound = false; if (!_err && doc) { if (doc && doc.rows.length) { // Read installed versions of all adapters for (let i = 0; i < doc.rows.length; i++) { diag.adapters[doc.rows[i].value.common.name] = { version: doc.rows[i].value.common.version, platform: doc.rows[i].value.common.platform }; if (doc.rows[i].value.common.name === 'vis') { visFound = true; } } } } // read number of vis datapoints if (visFound) { let visUtils = require(__dirname + '/lib/vis/states'); try { visUtils(objects, null, 0, null, function (err, points) { let total = null; let tasks = []; if (points && points.length) { for (let i = 0; i < points.length; i++) { if (points[i].id === 'vis.0.datapoints.total') { total = points[i].val; } tasks.push({ _id: points[i].id, type: 'state', native: {}, common: { name: 'Datapoints count', role: 'state', type: 'number', read: true, write: false }, state: { val: points[i].val, ack: true } }); } } if (total !== null) { diag.vis = total; } extendObjects(tasks, function () { if (callback) callback(diag); }); }); } catch (e) { logger.error('cannot call visUtils: ' + e); if (callback) callback(diag); } } else { if (callback) callback(diag); } }); }); }); }); } } // check if some IPv4 address found. If not try in 30 seconds one more time (max 10 times) function setIPs(ipList) { let _ipList = ipList || getIPs(); // check if IPs detected (because of DHCP delay) let found = false; for (let a = 0; a < _ipList.length; a++) { if (_ipList[a] === '127.0.0.1' || _ipList[a] === '::1/128' || !_ipList[a].match(/^\d+\.\d+\.\d+\.\d+$/)) continue; found = true; break; } // IPv4 address still not found, try again in 30 seconds if (!found && detectIpsCount < 10) { detectIpsCount++; setTimeout(function () { setIPs(); }, 30000); } else if (found) { // IPv4 found => write to object objects.getObject('system.host.' + hostname, function (err, oldObj) { let networkInterfaces = os.networkInterfaces(); if (JSON.stringify(oldObj.native.hardware.networkInterfaces) !== JSON.stringify(networkInterfaces) || JSON.stringify(oldObj.common.address) !== JSON.stringify(ipList)) { oldObj.common.address = ipList; oldObj.native.hardware.networkInterfaces = networkInterfaces; oldObj.from = 'system.host.' + tools.getHostName(); oldObj.ts = Date.now(); objects.setObject(oldObj._id, oldObj, function (err) { if (err) logger.error('Cannot write host object:' + err); }); } }); } else { logger.info('No IPv4 address found after 5 minutes.'); } } // write 10 objects each after other function extendObjects(tasks, callback) { if (!tasks || !tasks.length) { if (typeof callback === 'function') callback(); return; } let task = tasks.shift(); let state = task.state; if (state !== undefined) { delete task.state; } objects.extendObject(task._id, task, function () { if (state) { states.setState(task._id, state, function () { setImmediate(extendObjects, tasks, callback); }); } else { setImmediate(extendObjects, tasks, callback); } }); } function setMeta() { let id = 'system.host.' + hostname; objects.getObject(id, function (err, oldObj) { let newObj = { _id: id, type: 'host', common: { name: hostname, title: oldObj && oldObj.common && oldObj.common.title ? oldObj.common.title : ioPackage.common.title, installedVersion: version, platform: ioPackage.common.platform, cmd: process.argv[0] + ' ' + (process.execArgv.join(' ') + ' ').replace(/--inspect-brk=\d+ /, '') + process.argv.slice(1).join(' '), hostname: hostname, address: getIPs(), type: ioPackage.common.name }, native: { process: { title: process.title, versions: process.versions, env: process.env }, os: { hostname: hostname, type: os.type(), platform: os.platform(), arch: os.arch(), release: os.release(), endianness: os.endianness(), tmpdir: os.tmpdir() }, hardware: { cpus: os.cpus(), totalmem: os.totalmem(), networkInterfaces: {} } } }; if (oldObj && oldObj.common && oldObj.common.icon) { newObj.common.icon = oldObj.common.icon; } if (oldObj && oldObj.common && oldObj.common.color) { newObj.common.color = oldObj.common.color; } // remove dynamic information if (newObj.native && newObj.native.hardware && newObj.native.hardware.cpus) { for (let c = 0; c < newObj.native.hardware.cpus.length; c++) { if (newObj.native.hardware.cpus[c].times) delete newObj.native.hardware.cpus[c].times; } } if (oldObj && oldObj.native.hardware && oldObj.native.hardware.networkInterfaces) { newObj.native.hardware.networkInterfaces = oldObj.native.hardware.networkInterfaces; } if (oldObj) { delete oldObj.cmd; delete oldObj.from; delete oldObj.ts; delete oldObj.acl; } if (!oldObj || JSON.stringify(newObj) !== JSON.stringify(oldObj)) { newObj.from = 'system.host.' + tools.getHostName(); newObj.ts = Date.now(); objects.setObject(id, newObj, function (err) { if (err) logger.error('Cannot write host object:' + err); }); } setIPs(newObj.common.address); }); let tasks = []; let obj = { _id: id + '.cpu', type: 'state', common: { name: 'Controller - cpu usage in % of one core', type: 'number', read: true, write: false, min: 0, role: 'value', unit: '% of one core' }, native: {} }; tasks.push(obj); obj = { _id: id + '.cputime', type: 'state', common: { name: 'Controller - accumulated cputime in seconds', type: 'number', read: true, write: false, min: 0, role: 'value', unit: 'seconds' }, native: {} }; tasks.push(obj); obj = { _id: id + '.mem', type: 'state', common: { type: 'number', role: 'value', name: hostname + ' - memory usage in %', unit: '%', read: true, write: false, min: 0, max: 100 }, native: {} }; tasks.push(obj); obj = { _id: id + '.memHeapUsed', type: 'state', common: { type: 'number', role: 'value', name: 'Controller - heap memory used in MB', read: true, write: false, min: 0, unit: 'MB' }, native: {} }; tasks.push(obj); if (fs.existsSync('/proc/meminfo')) { obj = { _id: id + '.memAvailable', type: 'state', common: { type: 'number', role: 'value', name: hostname + ' - available memory from /proc/meminfo in MB', read: true, write: false, min: 0, unit: 'MB' }, native: {} }; tasks.push(obj); } obj = { _id: id + '.memHeapTotal', type: 'state', common: { type: 'number', role: 'value', name: 'Controller - heap memory reserved in MB', read: true, write: false, min: 0, unit: 'MB' }, native: {} }; tasks.push(obj); obj = { _id: id + '.memRss', type: 'state', common: { type: 'number', role: 'value', name: 'Controller - resident set size memory in MB', desc: 'RSS is the resident set size, the portion of the process\'s memory held in RAM', read: true, write: false, min: 0, unit: 'MB' }, native: {} }; tasks.push(obj); obj = { _id: id + '.uptime', type: 'state', common: { type: 'number', role: 'value', name: 'Controller - uptime in seconds', read: true, write: false, min: 0, unit: 'seconds' }, native: {} }; tasks.push(obj); obj = { _id: id + '.load', type: 'state', common: { unit: '', type: 'number', role: 'value', read: true, write: false, name: hostname + ' - load average 1min' }, native: {} }; tasks.push(obj); obj = { _id: id + '.alive', type: 'state', common: { name: hostname + ' - alive status', read: true, write: false, type: 'boolean', role: 'indicator' }, native: {} }; tasks.push(obj); obj = { _id: id + '.freemem', type: 'state', common: { name: hostname + ' - available RAM in MB', unit: 'MB', read: true, write: false, type: 'number', role: 'value' }, native: {} }; tasks.push(obj); obj = { _id: id + '.inputCount', type: 'state', common: { name: 'Controller - input level in events/15 seconds', desc: 'State\'s inputs in 15 seconds', type: 'number', read: true, write: false, role: 'value', unit: 'events/15 seconds' }, native: {} }; tasks.push(obj); obj = { _id: id + '.outputCount', type: 'state', common: { name: 'Controller - output level in events/15 seconds', desc: 'State\'s outputs in 15 seconds', type: 'number', read: true, write: false, role: 'value', unit: 'events/15 seconds' }, native: {} }; tasks.push(obj); config.system.checkDiskInterval = (config.system.checkDiskInterval !== 0) ? parseInt(config.system.checkDiskInterval, 10) || 300000 : 0; if (config.system.checkDiskInterval) { obj = { _id: id + '.diskSize', type: 'state', common: { name: hostname + ' - disk total size', desc: 'Disk size of logical volume where the server is installed in MiB', type: 'number', read: true, write: false, role: 'value', unit: 'MiB' }, native: {} }; tasks.push(obj); obj = { _id: id + '.diskFree', type: 'state', common: { name: hostname + ' - disk free size', desc: 'Free disk size of the logical volume where the server is installed in MiB', type: 'number', read: true, write: false, role: 'value', unit: 'MiB' }, native: {} }; tasks.push(obj); obj = { _id: id + '.diskWarning', type: 'state', common: { name: hostname + ' - disk warning level', desc: 'Show warning in admin if the free disk space is below this value', type: 'number', read: true, write: true, def: 5, role: 'level', unit: '%' }, native: {} }; tasks.push(obj); } // delete obsolete states objects.getObjectView('system', 'state', {startkey: 'system.host.' + hostname + '.', endkey: 'system.host.' + hostname + '.\u9999', include_docs: true}, function (_err, doc) { // identify existing states for deletion, because they are not in the new tasks-list let todelete = doc.rows.filter(out1 => !tasks.some(out2 => out1.id === out2._id)); if (todelete && todelete.length > 0) { delObjects(todelete