///////////////////////////////////////////////////////////////////////////////
/*
MonDeSi.js
COPYRIGHT
---------
Copyright (C) 2014-2023 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.
http://www.gnu.org/licenses/gpl.txt
VERSION
-------
26-SEP-2023 MGD double height checkbox
bugfix; modes 15 mins
04-MAR-2023 MGD add CPU modes alternate display
01-DEC-2020 MGD OPCOM monitor
29-OCT-2018 MGD bugfix; mdsiFromQuery() use decodeURIComponent()
20-MAR-2018 MGD mdsiFromConfig()
02-FEB-2014 MGD initial
*/
///////////////////////////////////////////////////////////////////////////////
// versions of MONDESI.EXE this JavaScript is compatible with
var mdsi_AcmeVersions = ['1.0.0','1.0.1'];
var mdsi_ExeVersions = ['6.0.0','6.0.1','6.1.0','6.1.1',
'6.2.0','6.2.1','6.2.2',
'7.0.0','7.1.0','7.2.0','7.3.0'];
var mdsi_ThousandComma = ',';
var mdsi_ColorConnected = 'black'
var mdsi_ColorDisconnected = 'red'
var mdsi_ResourcePath = '/mondesi/-/';
var mdsi_LoadedFrom = location.protocol + location.host;
var mdsi_AcmeIncompatible = 'JavaScript and Acme module incompatible!\n' +
'The multiple sites cannot interwork.'
var mdsi_ExeIncompatible = 'JavaScript and executable are incompatible!\n' +
'Reload and/or clear browser cache.';
var mdsi_AccessForbidden = false,
mdsi_Running = true;
var mdsi_BarHeight = 17, /* plus one height for border collapse */
mdsi_BarWidth = 300,
mdsi_BarLabelWidth = 60;
var mdsi_HistHeight = 100,
mdsi_HistWidth5 = 300,
mdsi_HistWidth15 = 920;
var mdsi_AccessData,
mdsi_ClusterData,
mdsi_DiskData,
mdsi_DynamicData,
mdsi_OpcomData,
mdsi_PerProcessData,
mdsi_TopProcessData,
mdsi_StaticData;
///////////////////////////////////////////////////////////////////////////////
////////////////////////////
// general infrastructure //
////////////////////////////
///////////////////////////////////////////////////////////////////////////////
var mdsi_MSIEversion = 0;
if (navigator.appName == 'Microsoft Internet Explorer')
{
var ua = navigator.userAgent;
var re = new RegExp('MSIE ([0-9]{1,}[\.0-9]{0,})');
if (re.exec(ua) != null) mdsi_MSIEversion = parseFloat(RegExp.$1);
}
// return a percentage string
function mdsiPercent(val,of)
{
if (!of) return '0%';
return (val * 100 / of).toFixed(0) + '%';
}
// return a parenthsized percentage string
function mdsiParenPercent(val,of)
{
if (!of) return '(0%)';
return '(' + (val * 100 / of).toFixed(0) + '%)';
}
// return a parenthsized percentage string separated by two spaces
function mdsiSpacedPercent(val,of)
{
if (!of) return ' (0%)';
return ' (' + (val * 100 / of).toFixed(0) + '%)';
}
String.prototype.htmlEscape = function()
{
var tagsToReplace = {
'&': '&',
'<': '<',
'>': '>'
};
return this.replace(/[&<>]/g, function(tag) {
return tagsToReplace[tag] || tag;
});
};
///////////////////////////////////////////////////////////////////////////////
/*
Quick and dirty "query string" parser. Usage:
var thisParam = mdsiFromQuery('this_param');
var thatParam = mdsiFromQuery('that_param');
It returns a corresponding element from the request query string or falls back
to a query string supplied by the executable in logical name "MONDESI_QUERY".
*/
function mdsiFromQuery (element)
{
if (!this.qs) this.qs = [];
if (!this.qs.length)
{
var ls = location.search;
ls = ls.substring(ls.indexOf('?')+1).split('&');
for (var idx = 0; idx < ls.length; idx++)
this.qs [ls[idx].split('=')[0]] =
decodeURIComponent(ls[idx].split('=')[1]);
}
if (this.qs[element]) return (this.qs[element]);
return (mdsiFromConfig(element));
}
///////////////////////////////////////////////////////////////////////////////
/*
Same as mdsiFromQuery() only different.
It uses a "query string" derived from executable static data "MONDESI_QUERY".
*/
function mdsiFromConfig (element)
{
if (!this.qs) this.qs = [];
if (!mdsi_StaticData) return null;
if (!this.qs.length)
{
var sd = mdsi_StaticData.MONDESI_QUERY;
if (sd.indexOf('?') >= 0) sd = sd.substring(sd.indexOf('?')+1);
sd = sd.split('&');
for (var idx = 0; idx < sd.length; idx++)
this.qs [sd[idx].split('=')[0]] = sd[idx].split('=')[1];
}
return (this.qs[element]);
}
///////////////////////////////////////////////////////////////////////////////
/*
Dynamically modify a stylesheet.
*/
function mdsiCSS (selector, property, value)
{
for (var idx = 0; idx < document.styleSheets.length; idx++)
{
var sheet = document.styleSheets[idx];
try {
var rule = selector + '{' + property + ':' + value + ';}';
sheet.insertRule(rule,sheet.cssRules.length);
}
catch(err) {
try {
sheet.addRule(selector,property+':'+value);
}
catch(err) {}
}
}
}
///////////////////////////////////////////////////////////////////////////////
/////////
// IPC //
/////////
///////////////////////////////////////////////////////////////////////////////
var mdsi_StreamingData = false;
function mdsiStreamData (start)
{
// close any existing connections
mdsi_StreamingData = false;
acmeIpcClose();
if (!start) return;
if (acmeIpcOpen()) mdsi_StreamingData = true;
}
///////////////////////////////////////////////////////////////////////////////
/*
Receive JSON data from the executable.
*/
function mdsiReceiveData (data)
{
data = JSON.parse(data);
if (data.$data == 'access')
{
mdsi_AccessData = data;
if (mdsi_AccessData.process == 0)
$byId('checkboxProcess').disabled = true;
else
$byId('checkboxProcess').disabled = false;
}
else
if (data.$data == 'alert')
mdsiAlert(data.time,data.message);
else
if (data.$data == 'cluster')
mdsi_ClusterData = data;
else
if (data.$data == 'disk')
{
mdsiReceiveDiskData(data);
if (mdsi_DiskDisplay && mdsi_UpdateDisplay) mdsiGraphDisk();
}
else
if (data.$data == 'dynamic')
{
mdsiStoreDynamicData(data);
if (mdsi_UpdateDisplay) mdsiGraphDynamic();
if (mdsiGetData('opcomLength'))
$byId('checkboxOpcom').disabled = false;
else
$byId('checkboxOpcom').disabled = true;
if (mdsi_OpcomDisplay && mdsiGetData('opcomAudit'))
$byId('checkboxAudit').disabled = false;
else
$byId('checkboxAudit').disabled = true;
}
else
if (data.$data == 'opcom')
{
mdsi_OpcomData = data;
if (!$byId('checkboxOpcom').disabled)
{
if ($byId('checkboxOpcom').checked)
{
mdsi_OpcomDisplay = true;
$byId('opcom').style.display = 'block';
}
}
if (mdsi_OpcomDisplay && mdsi_UpdateDisplay) mdsiDisplayOpcom();
}
else
if (data.$data == 'process')
{
mdsi_TopProcessData = data;
if (mdsi_ProcessDisplay && mdsi_UpdateDisplay) mdsiDisplayTopProcess();
}
else
if (data.$data == 'per-process')
{
mdsiReceivePerProcessData(data);
if (mdsi_PerProcessPid && mdsi_UpdateDisplay) mdsiDisplayPerProcess();
}
else
if (data.$data == 'static')
{
mdsi_StaticData = data;
if (mdsi_StaticData.clusterMember)
$byId('checkboxCluster').disabled = false;
// and add the node to the window title
var json = '{"$AddToTitle":true,"node":"' +
mdsi_StaticData.nodeName + '"}';
window.parent.postMessage(json,'*');
}
else
if (data.$data != 'runtime')
{
mdsiAlert('DATA ERROR: '+data.$data);
console.log(data);
}
if (mdsi_InsightDisplay) mdsiDisplayInsight(data);
}
///////////////////////////////////////////////////////////////////////////////
/*
Append this message to the alert section in the node summary area.
*/
function mdsiAlert (time,msg)
{
if (typeof msg == 'undefined')
{
msg = time;
time = mdsiGetData('vmsTime');
}
var alert = $byId('alert');
var closedAlert = $byId('closedAlert');
if (typeof msg == 'string')
{
if (mdsi_AlertsDisplay)
{
if (msg.substr(0,1) == '#') msg = msg.substr(1);
if (msg.substr(0,1) == '!')
var html = '' + time +
' ' + msg.substr(1) + '';
else
var html = '' + time +
' ' + msg + '';
if (alert.innerHTML) alert.innerHTML += '
';
alert.innerHTML += html;
closedAlert.innerHTML = html;
alert.onclick = mdsiAlert;
}
}
else
alert.innerHTML = closedAlert.innerHTML = '';
acmeAdjustSize();
}
///////////////////////////////////////////////////////////////////////////////
/*
Insert the latest disk data into the most recent dynamic store entry.
*/
var mdsi_PrevDelta = 0;
function mdsiReceiveDiskData (data)
{
if (typeof data == 'undefined')
{
// (re)initialise
mdsi_DiskData = null;
mdsi_PrevDelta = 0;
return;
}
mdsi_DiskData = data;
var slen = mdsi_DynamicStore.length;
var latest = mdsi_DynamicStore[slen-1];
var dcnt = mdsiGetData('diskCount');
for (var dnum = 0; dnum < dcnt; dnum++)
{
var disk = 'disk' + dnum;
latest[disk+'dev'] = data.devices[dnum].dev;
latest[disk+'err'] = data.devices[dnum].err;
latest[disk+'free'] = data.devices[dnum].free;
latest[disk+'iops'] = data.devices[dnum].iops;
latest[disk+'mvip'] = data.devices[dnum].mvip;
latest[disk+'qlen'] = data.devices[dnum].qlen;
latest[disk+'vol'] = data.devices[dnum].vol;
var size = latest[disk+'size'] = data.devices[dnum].size;
if (slen <= 2)
{
latest[disk+'delta'] = 0;
latest[disk+'delval'] = 0;
latest[disk+'delsym'] = '';
latest[disk+'qmax'] = 0;
continue;
}
// otherwise recalculate the free space consumption rate
var max = 0;
var min = 9007199254740992;
var dfree = disk + 'free';
var dqlen = disk + 'qlen';
var qmax = 0;
for (var idx = 0; idx < slen; idx++)
{
var free = mdsi_DynamicStore[idx][dfree];
if (free > max) max = free;
if (free < min) min = free;
if (typeof mdsi_DynamicStore[idx][dqlen] != 'undefined' &&
mdsi_DynamicStore[idx][dqlen] > qmax)
qmax = mdsi_DynamicStore[idx][dqlen];
}
latest[disk+'qmax'] = qmax;
var delta = max - min;
latest[disk+'delta'] = delta;
latest[disk+'delval'] = latest[dfree] - min;
latest[disk+'delsym'] = '';
// disk data is collected on-demand - fudge any missing elements
for (var idx = 0; idx < slen-1; idx++)
if (typeof mdsi_DynamicStore[idx][dfree] == 'undefined')
mdsi_DynamicStore[idx][dfree] = max;
else
break;
if (latest[dfree] < mdsi_DynamicStore[0][dfree])
latest[disk+'delsym'] = '\u21e3';
else
if (latest[dfree] > mdsi_DynamicStore[0][dfree])
latest[disk+'delsym'] = '\u21e1';
if (delta != mdsi_PrevDelta)
for (var idx = 0; idx < slen; idx++)
mdsi_DynamicStore[idx][disk+'delval'] =
mdsi_DynamicStore[idx][dfree] - min;
mdsi_PrevDelta = delta;
}
}
///////////////////////////////////////////////////////////////////////////////
/*
Insert the latest per-process graphing (munged) data into the most recent
dynamic store entry.
*/
function mdsiReceivePerProcessData (data)
{
if (typeof data == 'undefined')
{
// (re)initialise
mdsi_PerProcessData = null;
return;
}
mdsi_PerProcessData = data;
if (!data.graph) return;
var latest = mdsi_DynamicStore[mdsi_DynamicStore.length-1];
var pid = data.PID;
latest[pid+'CPU'] = data.graph.CPU;
latest[pid+'SEK'] = data.graph.SEK;
latest[pid+'BufIO'] = data.graph.bufIO;
latest[pid+'DirIO'] = data.graph.dirIO;
latest[pid+'PageFaults'] = data.graph.pageFlts;
latest[pid+'GlobalPages'] = data.graph.gpgCnt;
latest[pid+'PageFileUsed'] = data.graph.pgFlQuota - data.graph.pagFilCnt;
latest[pid+'PageFileQuota'] = data.graph.pgFlQuota;
latest[pid+'PrivatePages'] = data.graph.ppgCnt;
latest[pid+'WorkingSet'] = data.graph.gpgCnt + data.graph.ppgCnt;
latest[pid+'WorkingExtent'] = data.graph.wsExtent;
}
///////////////////////////////////////////////////////////////////////////////
////////////////
// data store //
////////////////
///////////////////////////////////////////////////////////////////////////////
var mdsi_DynamicStore = [];
var mdsi_StoreSeconds = 0;
var mdsi_StoreTimeStamp = 0;
function mdsiStoreDynamicData (data)
{
var seconds = Date.now() / 1000;
if (seconds - mdsi_StoreTimeStamp > mdsi_StoreSeconds) mdsiZeroClick();
mdsi_StoreTimeStamp = seconds;
mdsi_DynamicData = data;
var store = mdsi_DynamicStore;
store.push(data);
if (mdsi_StaticData.clusterMember)
{
var value;
var latest = store[store.length-1];
value = mdsiGetData('mscpRead') + mdsiGetData('mscpWrite');
if (value) latest['mscpTotal'] = value;
value = mdsiGetData('clusterMsgRcvd') + mdsiGetData('clusterMsgSent');
if (value) latest['clusterMsgTotal'] = value;
}
/* periodically remove old data */
if (store.length > (mdsi_StoreSeconds/mdsi_StaticData.interval)+4)
store.splice(0,3);
}
// convenience routine to return size of store
function mdsiStoreSize ()
{
return mdsi_DynamicStore.length;
}
// convenience routine to set size of store
function mdsiStoreSeconds (secs)
{
if (mdsi_StoreSeconds < secs) mdsi_StoreSeconds = secs;
}
///////////////////////////////////////////////////////////////////////////////
/*
Function name says it all!
*/
function mdsiZeroClick ()
{
mdsi_DynamicData = null;
mdsi_TopProcessData = null;
mdsi_DynamicStore = [];
mdsiReceiveDiskData();
mdsiReceivePerProcessData();
if (mdsi_UpdateDisplay) mdsiGraphDynamic();
if (mdsi_DiskDisplay) mdsiGraphDisk();
if (mdsi_ProcessDisplay) mdsiDisplayTopProcess();
if (mdsi_PerProcessPid) mdsiDisplayPerProcess();
}
///////////////////////////////////////////////////////////////////////////////
/*
Return a datum from a data store (if the datum does not exist then return zero
or the 'retval' argument) by default from the array of summary data if the
first parameter is a reference to a specific store than from that.
*/
function mdsiGetDataWithCommas(datum,store,idx)
{
return mdsiWithCommas(mdsiGetData(datum,store,idx));
}
function mdsiGetData(datum,store,idx,retval)
{
if (typeof retval == 'undefined') retval = 0;
if (typeof store == 'undefined') store = mdsi_DynamicStore;
if (store == null) store = mdsi_DynamicStore;
if (!(store.constructor === Array))
{
// not a (array of) data store
if (typeof store[datum] == 'undefined')
return retval;
else
return store[datum];
}
// if not specified get the most recent
if (typeof idx == 'undefined' || idx == -1) idx = store.length - 1;
// if no datum supplied return a reference to the array element
if (typeof datum == 'undefined') return (store[idx]);
if (typeof store == 'undefined' ||
typeof store[idx] == 'undefined' ||
typeof store[idx][datum] == 'undefined')
return retval;
else
return store[idx][datum];
}
///////////////////////////////////////////////////////////////////////////////
///////////
// print //
///////////
///////////////////////////////////////////////////////////////////////////////
var mdsi_PrintClickTimer = null;
var mdsi_PrintInProgress = false;
var mdsi_PrintTimeout = 5; // seconds
var mdsi_PrintUpdate;
function mdsiPrintClick ()
{
if (mdsi_PrintInProgress)
mdsiAfterPrint();
else
{
mdsiBeforePrint();
setTimeout('window.print()',100);
}
return true;
}
function mdsiBeforePrint ()
{
if (mdsi_PrintInProgress) return;
mdsi_PrintInProgress = true;
mdsi_PrintUpdate = mdsi_UpdateDisplay;
mdsiUpdateClick(false);
clearTimeout(mdsi_PrintClickTimer);
mdsi_PrintClickTimer = null;
mdsi_PrintClickTimer = setTimeout('mdsiPrintClick()',mdsi_PrintTimeout*1000);
};
function mdsiAfterPrint()
{
if (!mdsi_PrintInProgress) return;
mdsi_PrintInProgress = false;
clearTimeout(mdsi_PrintClickTimer);
mdsi_PrintClickTimer = null;
if (mdsi_PrintUpdate) mdsiUpdateClick(true);
};
// NOTE: this is in-line code
var mdsi_MatchMedia;
if (window.matchMedia)
{
mdsi_MatchMedia = window.matchMedia('print');
mdsi_MatchMedia.addListener(function(mql) {
if (mql.matches)
mdsiBeforePrint();
else
mdsiAfterPrint();
});
}
// a la MSIE
window.onbeforeprint = mdsiBeforePrint;
window.onafterprint = mdsiAfterPrint;
///////////////////////////////////////////////////////////////////////////////
////////////////
// build page //
////////////////
///////////////////////////////////////////////////////////////////////////////
/*
*/
var mdsi_OnOpenTimer = null;
function mdsiOnOpen ()
{
if ($WebSocket)
{
$byId('sumNodeName').style.color = mdsi_ColorConnected;
$byId('monitorClosed').style.color = mdsi_ColorConnected;
// inform the process of the current interests
var params = mdsiTheseData();
acmeIpcSend(params);
}
else
{
var disc = "$byId('sumNodeName').style.color = \
$byId('monitorClosed').style.color = mdsi_ColorConnected;";
mdsi_OnOpenTimer = setTimeout(disc,2000);
clearTimeout(mdsi_OnCloseTimer);
mdsi_OnCloseTimer = null;
}
}
///////////////////////////////////////////////////////////////////////////////
/*
*/
var mdsi_OnCloseTimer = null;
function mdsiOnClose ()
{
if ($WebSocket)
{
$byId('sumNodeName').style.color = mdsi_ColorDisconnected;
$byId('monitorClosed').style.color = mdsi_ColorDisconnected;
}
else
{
// delay a little for re-request
var disc = "$byId('sumNodeName').style.color = \
$byId('monitorClosed').style.color = mdsi_ColorDisconnected;";
mdsi_OnCloseTimer = setTimeout(disc,2000);
clearTimeout(mdsi_OnOpenTimer);
mdsi_OnOpenTimer = null;
}
}
///////////////////////////////////////////////////////////////////////////////
/*
Primary function called by acme.js when primary JavaScript file loaded.
*/
function $mondesi ()
{
acmeIpcOnOpen(mdsiOnOpen);
acmeIpcOnClose(mdsiOnClose);
acmeIpcOnMessage(mdsiReceiveData);
acmeLoadFile('display.js');
acmeLoadFile('graph.js');
acmeLoadFile('mondesi.css');
acmeLoadFile('build.js','mdsiBuildPage()');
}
if (mdsi_AcmeVersions.indexOf($AcmeVersion) == -1)
{
alert(mdsi_AcmeIncompatible);
throw new Error(mdsi_AcmeIncompatible);
}
if (mdsi_ExeVersions.indexOf($ExeVersion) == -1)
{
alert(mdsi_ExeIncompatible);
throw new Error(mdsi_ExeIncompatible);
}
///////////////////////////////////////////////////////////////////////////////