yunkong2.js-controller
Version:
Updated by reinstall.js on 2018-06-11T15:19:56.688Z
1,240 lines (1,162 loc) • 124 kB
JavaScript
/**
* 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