/////////////////////////////////////////////////////////////////////////////// /* 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); } ///////////////////////////////////////////////////////////////////////////////