public/admin/SAGE2_performance.js
// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2017
"use strict";
/**
* SAGE2 Performance monitoring display
*
* @module client
* @submodule SAGE2_Performance
* @class SAGE2_Performance
*/
/*global SAGE2_init: true, d3: true, drawDisplaySM: true, showDisplayClientsHistory: true,
setupLineChart: true, charts: true */
/**
* Global variables
*/
//One object that holds all performance related information
var performanceMetrics = {
staticInformation: null,
cpuLoad: null,
serverLoad: null,
serverTraffic: null,
network: null,
memUsage: null,
movingAvg1Minute: {
cpuLoad: null,
cpuCoresLoad: null,
serverLoad: null,
serverTraffic: null,
network: null,
memUsage: null
},
movingAvgEntireDuration: {
cpuLoad: null,
serverLoad: null,
serverTraffic: null,
network: null,
memUsage: null
},
//Historical data for establishing time line
history: {
cpuLoad: [],
serverLoad: [],
serverTraffic: [],
network: [],
memUsage: []
}
};
var clients = {
hardware: [],
performanceMetrics: [],
history: []
};
var durationInMinutes = 5;
// default to 2 second - 'normal'
var samplingInterval = 2;
var clientColorMap = {};
var selectedDisplayClientIDList = [];
var colors = [];
/**
* Entry point of the performance application
*
* @method SAGE2_init
*/
function SAGE2_init() {
// Connect to the server
var wsio = new WebsocketIO();
console.log("Connected to server: ", window.location.origin);
// Get the cookie for the session, if there's one
var session = getCookie("session");
// Callback when socket opens
wsio.open(function() {
console.log("open websocket");
// Setup message callbacks
setupListeners(wsio);
// Register to the server as a console
var clientDescription = {
clientType: "performance",
requests: {
config: true,
version: true,
time: false,
console: false
},
session: session
};
wsio.emit('addClient', clientDescription);
});
// Socket close event (ie server crashed)
wsio.on('close', function() {
var refresh = setInterval(function() {
// make a dummy request to test the server every 2 sec
var xhr = new XMLHttpRequest();
xhr.open("GET", "/", true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("server ready");
// when server ready, clear the interval callback
clearInterval(refresh);
// and reload the page
window.location.reload();
}
};
xhr.send();
}, 2000);
});
}
/**
* Place callbacks on various messages from the server
*
* @method setupListeners
* @param wsio {Object} websocket
*/
function setupListeners(wsio) {
// Get elements from the DOM
var terminal1 = document.getElementById('terminal1');
var heading1 = document.getElementById('serverheading');
// Got a reply from the server
wsio.on('initialize', function() {
colors.push(...d3.schemeCategory20);
colors.push(...d3.schemeCategory20b);
initializeCharts();
});
// Server sends the SAGE2 version
wsio.on('setupSAGE2Version', function(data) {
console.log('SAGE2: version', data.base, data.branch, data.commit, data.date);
});
// Server sends the wall configuration
wsio.on('setupDisplayConfiguration', function() {
console.log('wall configuration');
});
// Server sends hardware and performance data
wsio.on('serverHardwareInformation', function(data) {
var msg = "";
if (data) {
performanceMetrics.staticInformation = data;
msg += 'System: ' + data.system.manufacturer + ' ' +
data.system.model + '\n';
msg += 'Hostname: ' + data.hostname + '\n';
msg += 'OS: ' + data.os.platform + ' ' +
data.os.arch + ' ' + data.os.distro + ' ' + data.os.release + '\n';
msg += 'CPU: ' + data.cpu.manufacturer + ' ' + data.cpu.brand + ' ' +
data.cpu.speed + 'Ghz ' + data.cpu.cores + 'cores\n';
// Sum up all the memory banks
var totalMem = data.memLayout.reduce(function(sum, value) {
return sum + value.size;
}, 0);
var memInfo = getNiceNumber(totalMem);
msg += 'RAM: ' + memInfo.number + memInfo.suffix + '\n';
// iterates over the GPU list
for (let i = data.graphics.controllers.length - 1; i >= 0; i--) {
let gpu = data.graphics.controllers[i];
let gpuMem = getNiceNumber(gpu.vram);
msg += 'GPU: ' + gpu.vendor + ' ' + gpu.model + ' ' +
gpuMem.number + gpuMem.suffix + ' VRAM\n';
}
// if there's no GPU recognized
if (data.graphics.controllers.length === 0) {
msg += 'GPU: -\n';
}
// Set the name of the server in the page
if (heading1) {
if (data.servername.length > 0) {
heading1.textContent = 'Server: ' + data.servername + ' (' + data.serverhost + ')';
} else {
heading1.textContent = 'Server: ' + data.serverhost;
}
}
}
// Added content
terminal1.textContent += msg;
// automatic scrolling to bottom
terminal1.scrollTop = terminal1.scrollHeight;
});
wsio.on('addDisplayHardwareInformation', function(data) {
if (Array.isArray(data) === true) {
clients.hardware = data;
} else {
clients.hardware.push(data);
}
showDisplayHardwareInformation();
});
wsio.on('removeDisplayHardwareInformation', function(data) {
if (data.id !== null && data.id !== undefined) {
var closedDisplay = clients.hardware.findIndex(function(d) {
return d.id === data.id;
});
if (closedDisplay > -1) {
clients.hardware.splice(closedDisplay, 1);
}
}
showDisplayHardwareInformation();
});
wsio.on('performanceData', function(data) {
if (Array.isArray(data.cpuLoad) === true) {
// History has been sent
saveData('cpuLoad', data.cpuLoad, true);
saveData('memUsage', data.memUsage, true);
saveData('network', data.network, true);
saveData('serverLoad', data.serverLoad, true);
saveData('serverTraffic', data.serverTraffic, true);
clients.history = data.clients;
} else {
// Current values
if (data.durationInMinutes) {
durationInMinutes = data.durationInMinutes;
}
if (data.samplingInterval) {
samplingInterval = data.samplingInterval;
}
saveData('cpuLoad', data.cpuLoad);
saveData('memUsage', data.memUsage);
saveData('network', data.network);
saveData('serverLoad', data.serverLoad);
saveData('serverTraffic', data.serverTraffic);
if (data.displayPerf !== null && data.displayPerf !== undefined && data.displayPerf.length > 0) {
clients.performanceMetrics = data.displayPerf;
var now = clients.performanceMetrics[0].date;
clients.performanceMetrics.sort(function(a, b) {
return a.clientID - b.clientID;
});
clients.history.push(...clients.performanceMetrics);
var durationAgo = now - durationInMinutes * (60 * 1000);
removeObjectsFromArrayOnPropertyValue(clients.history, 'date', durationAgo, 'lt');
if (clients.performanceMetrics.length > clients.hardware.length) {
wsio.emit("requestClientUpdate");
}
} else {
clients.performanceMetrics = [];
var smDiv = document.getElementById('smallmultiplediv');
smDiv.style.height = 0 + 'px';
var displayMetricDiv = document.getElementById('displaypanecontainer');
displayMetricDiv.style.height = 0 + 'px';
}
findMaxValues();
initializeCharts();
drawCharts();
}
if (performanceMetrics.staticInformation === null ||
performanceMetrics.staticInformation === undefined) {
wsio.emit("requestClientUpdate");
}
});
// Socket close event (ie server crashed)
wsio.on('close', function(evt) {
//showSAGE2Message("Server offline");
var refresh = setInterval(function() {
// make a dummy request to test the server every 2 sec
var xhr = new XMLHttpRequest();
xhr.open("GET", "/", true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("server ready");
// when server ready, clear the interval callback
clearInterval(refresh);
// and reload the page
window.location.reload();
}
};
xhr.send();
}, 2000);
});
}
function drawCharts() {
updateLineChart('cpuload', performanceMetrics.history.cpuLoad);
updateLineChart('serverload', performanceMetrics.history.serverLoad);
updateLineChart('memusage', performanceMetrics.history.memUsage);
updateLineChart('servermem', performanceMetrics.history.serverLoad);
updateLineChart('servertraffic', performanceMetrics.history.serverTraffic);
updateLineChart('systemtraffic', performanceMetrics.history.network);
cleanUpSelectedDisplayList();
drawDisplaySM();
showDisplayClientsHistory();
}
function handlePageResize() {
// body.style.webkitTransform = "scale(" + scaleFactor + ")";
// body.style.mozTransform = "scale(" + scaleFactor + ")";
// body.style.transform = "scale(" + scaleFactor + ")";
d3.selectAll('svg')
.attr("width", function(d) {
return this.parentNode.clientWidth;
})
.attr("height", function(d) {
return this.parentNode.clientHeight;
})
.attr("viewbox", function(d) {
var width = this.parentNode.clientWidth;
var height = this.parentNode.clientHeight;
//console.log(this.parentNode.id, width);
return "0, 0, 1000, " + parseInt(1000 * (height / width));
});
initializeCharts();
drawCharts();
}
//
// Show error message
// if time given as parameter in seconds, close after delay
//
function showSAGE2Message(message, delay) {
// Display server offline message
}
function showDisplayHardwareInformation() {
var terminal2 = document.getElementById('terminal2');
var data = clients.hardware;
if (data.length > 0) {
var msg = "";
for (let i = 0; i < data.length; i++) {
let disp = data[i];
msg += '<span style="color:cyan;">Display ' + disp.clientID + ' </span>: ' + disp.system.manufacturer + ' ' +
disp.system.model + '\n';
msg += 'Hostname: ' + disp.hostname + '\n';
msg += 'OS: ' + disp.os.platform + ' ' +
disp.os.arch + ' ' + disp.os.distro + ' ' + disp.os.release + '\n';
msg += 'CPU: ' + disp.cpu.manufacturer + ' ' + disp.cpu.brand + ' ' +
disp.cpu.speed + 'Ghz ' + disp.cpu.cores + 'cores\n';
// Sum up all the memory banks
var totalMem = disp.memLayout.reduce(function(sum, value) {
return sum + value.size;
}, 0);
var memInfo = getNiceNumber(totalMem);
msg += 'RAM: ' + memInfo.number + memInfo.suffix + '\n';
var gpuMem = getNiceNumber(disp.graphics.controllers[0].vram);
// not very good on Linux (need to check nvidia tools)
msg += 'GPU: ' + disp.graphics.controllers[0].vendor + ' ' +
disp.graphics.controllers[0].model + ' ' +
gpuMem.number + gpuMem.suffix + ' VRAM\n';
// Assign colors to display clients
if (clientColorMap.hasOwnProperty(disp.id) === false) {
clientColorMap[disp.id] = getNewColor(clientColorMap);
}
}
// Added content
terminal2.innerHTML = msg;
// automatic scrolling to bottom
terminal2.scrollTop = terminal2.scrollHeight;
} else {
terminal2.innerHTML = 'No Electron Display Client active.';
}
}
/**
* Helper function to convert a number to shorter format with
* appropriate suffix determined (K for Kilo and so on)
*
* @method getNiceNumber
* @param {number} number - large number
* @param {Boolean} giga - using 1000 or 1024
*/
function getNiceNumber(number, giga) {
var suffix;
var idx = 0;
var base = giga ? 1000 : 1024;
if (giga) {
suffix = ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb'];
} else {
suffix = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
}
while (number >= base) {
number = number / base;
idx = idx + 1; // For every 1000 or 1024, a new suffix is chosen
}
return {number: number.toFixed(0), suffix: suffix[idx]};
}
/**
* Helper function to get the next power of ten closest to a number
*
* @method getNextPowerOfTen
* @param {number} number - some number
*/
function getNextPowerOfTen(number) {
var powerOfTen = 1;
while (number >= powerOfTen) {
powerOfTen = powerOfTen * 10;
}
return powerOfTen;
}
/**
* Helper function to get a percentage value
*
* @method getPercentString
* @param {number} val - one part(of a total) for which percentage is to be computed
* @param {number} remaining - remaining part (of the total)
*/
function getPercentString(val, remaining) {
// Rounding off
val = parseInt(val);
remaining = parseInt(remaining);
var percent = val * 100 / (val + remaining);
return d3.format("3.0f")(percent);
}
/**
* Saves metric data into current value placeholder and history list
*
* @method saveData
* @param {string} metric - metric for which data is being saved
* @param {object} data - current metric values obtained
*/
function saveData(metric, data, history) {
// Number of samples in the history
// time in seconds
if (history === true) {
performanceMetrics.history[metric] = data;
} else if (data !== null) {
// Current value
performanceMetrics[metric] = data;
var now = data.date;
// Add data to the historic list
performanceMetrics.history[metric].push(data);
var durationAgo = now - durationInMinutes * (60 * 1000);
removeObjectsFromArrayOnPropertyValue(performanceMetrics.history[metric], "date", durationAgo, 'lt');
}
}
/**
* Helper function to format memory usage info in a string
*
* @method formatMemoryString
* @param {number} used - amount of memory used
* @param {number} free - amount of memory free
* @param {boolean} short - flag to request a short version of the string
*/
function formatMemoryString(used, free, short) {
var total = used + free;
var usedPercent = used / total * 100;
used = getNiceNumber(used);
total = getNiceNumber(total);
usedPercent = d3.format('3.0f')(usedPercent);
var printString;
if (short === true) {
printString = usedPercent;
} else {
printString = usedPercent + "% (" + used.number + used.suffix + ") of " +
total.number + total.suffix;
}
return printString;
}
function initializeCharts() {
var yAxisFormatLoad = function(d) {
return (d * 100) + "%";
};
var yAxisFormatMemory = function(d) {
var mem = getNiceNumber(d
* (performanceMetrics.memUsage.used + performanceMetrics.memUsage.free));
return mem.number + mem.suffix;
};
var yAxisFormatNetworkServer = function(d) {
var mem = getNiceNumber(d * performanceMetrics.serverTrafficMax, true);
return mem.number + mem.suffix;
};
var yAxisFormatNetworkSystem = function(d) {
var mem = getNiceNumber(d * performanceMetrics.networkMax, true);
return mem.number + mem.suffix;
};
var yAxisFormatSAGE2Memory = function(d) {
var mem = getNiceNumber(d * performanceMetrics.sage2MemoryMax, true);
return mem.number + mem.suffix;
};
var currentCPULoadText = function() {
if (performanceMetrics.cpuLoad) {
var cpuLoad = performanceMetrics.cpuLoad;
return "Current: " + getPercentString(cpuLoad.load, cpuLoad.idle) + "%";
} else {
return "";
}
};
setupLineChart('cpuload', 'CPU Load', function(d) {
return d.load / (d.load + d.idle);
}, yAxisFormatLoad, currentCPULoadText, 0.5);
var currentMemUsageText = function() {
if (performanceMetrics.memUsage) {
var memUsage = performanceMetrics.memUsage;
return "Current: " + formatMemoryString(memUsage.used, memUsage.total - memUsage.used);
} else {
return "";
}
};
setupLineChart('memusage', 'System Memory', function(d) {
return d.used / (d.used + d.free);
}, yAxisFormatMemory, currentMemUsageText, 0.7);
var currentServerLoadText = function() {
if (performanceMetrics.serverLoad) {
var serverLoad = performanceMetrics.serverLoad;
return "Current: " + d3.format('3.0f')(serverLoad.cpuPercent) + "%";
} else {
return "";
}
};
setupLineChart('serverload', 'SAGE2 Load', function(d) {
return d.cpuPercent / 100;
}, yAxisFormatLoad, currentServerLoadText, 0.5);
var currentServerMemText = function() {
if (performanceMetrics.memUsage) {
var memUsage = performanceMetrics.memUsage;
var servermem = performanceMetrics.serverLoad.memResidentSet;
return "Current: " + formatMemoryString(servermem, memUsage.total - servermem);
} else {
return "";
}
};
setupLineChart('servermem', 'SAGE2 Memory', function(d) {
return d.memResidentSet / performanceMetrics.sage2MemoryMax;
}, yAxisFormatSAGE2Memory, currentServerMemText, 0);
var currentServerTrafficText = function() {
if (performanceMetrics.serverTraffic) {
var serverTraffic = performanceMetrics.serverTraffic;
var currentTraffic = getNiceNumber(serverTraffic.totalOutBound + serverTraffic.totalInBound, true);
return "Current: " + currentTraffic.number + currentTraffic.suffix;
} else {
return "";
}
};
setupLineChart('servertraffic', 'SAGE2 Traffic', function(d) {
return (d.totalOutBound + d.totalInBound) / performanceMetrics.serverTrafficMax;
}, yAxisFormatNetworkServer, currentServerTrafficText, 0);
var currentSystemTrafficText = function() {
if (performanceMetrics.network) {
var network = performanceMetrics.network;
var currentTraffic = getNiceNumber(network.totalOutBound + network.totalInBound, true);
return "Current: " + currentTraffic.number + currentTraffic.suffix;
} else {
return "";
}
};
setupLineChart('systemtraffic', 'System Traffic', function(d) {
return (d.totalOutBound + d.totalInBound) / performanceMetrics.networkMax;
}, yAxisFormatNetworkSystem, currentSystemTrafficText, 0);
}
function findMaxValues() {
var totalTrafficList = performanceMetrics.history.network.filter(function(d) {
return d !== null && d !== undefined;
}).map(function(d) {
return d.totalOutBound + d.totalInBound;
});
performanceMetrics.networkMax = getNextPowerOfTen(d3.max(totalTrafficList));
var totalServerTrafficList = performanceMetrics.history.serverTraffic.filter(function(d) {
return d !== null && d !== undefined;
}).map(function(d) {
return d.totalOutBound + d.totalInBound;
});
performanceMetrics.serverTrafficMax = getNextPowerOfTen(d3.max(totalServerTrafficList));
var totalSage2MemoryList = performanceMetrics.history.serverLoad.filter(function(d) {
return d !== null && d !== undefined;
}).map(function(d) {
return d.memResidentSet;
});
performanceMetrics.sage2MemoryMax = getNextPowerOfTen(d3.max(totalSage2MemoryList));
var totalClientMemoryList = clients.history.filter(function(d) {
return d !== null && d !== undefined;
}).map(function(d) {
var mem = d.memUsage;
return mem.used + mem.free;
});
clients.systemMemoryMax = d3.max(totalClientMemoryList);
var totClientDisplayMemList = clients.history.filter(function(d) {
return d !== null && d !== undefined;
}).map(function(d) {
var clientLoad = d.clientLoad;
return clientLoad.memResidentSet;
});
clients.displayMemoryMax = getNextPowerOfTen(d3.max(totClientDisplayMemList));
}
function removeObjectsFromArrayOnPropertyValue(array, property, value, condition) {
// Current value
var mapFunc;
switch (condition) {
case 'lt':
mapFunc = function(d) {
if ((d !== null) && (d !== undefined)) {
return d[property] < value;
} else {
return false;
}
};
break;
case 'gt':
mapFunc = function(d) {
if ((d !== null) && (d !== undefined)) {
return d[property] > value;
} else {
return false;
}
};
break;
case 'lte':
mapFunc = function(d) {
if ((d !== null) && (d !== undefined)) {
return d[property] <= value;
} else {
return false;
}
};
break;
case 'gte':
mapFunc = function(d) {
if ((d !== null) && (d !== undefined)) {
return d[property] >= value;
} else {
return false;
}
};
break;
case 'eq':
default:
mapFunc = function(d) {
if ((d !== null) && (d !== undefined)) {
return d[property] === value;
} else {
return false;
}
};
break;
}
var results = array.map(mapFunc);
var count = 0;
for (var i = results.length - 1; i >= 0; i--) {
if (results[i] === true) {
array.splice(i, 1);
count++;
}
}
return count;
}
function getNewColor(colorMap) {
var usedList = Object.keys(colorMap);
var len = usedList.length;
return colors[len % colors.length];
}
function checkForNegatives(obj) {
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
if (typeof obj[k] === 'number' && isNaN(obj[k]) === false && obj[k] < 0) {
return true;
}
}
}
return false;
}
function updateLineChart(chartId, data, key, filterlist) {
data = data.filter(function(d) {
return d !== null && d !== undefined;
});
var now = performanceMetrics.cpuLoad.date;
var entireDurationInMilliseconds = durationInMinutes * 60 * 1000;
var timeDomain = [now - entireDurationInMilliseconds, now];
var chart = charts[chartId];
chart.scaleX.domain(timeDomain);
chart.xAxis.call(chart.xAxisFunc);
chart.yAxis.call(chart.yAxisFunc);
if (chart.current) {
chart.current.text(chart.currentTextFunc());
}
if (key !== null && key !== undefined) {
var nestedData = d3.nest()
.key(d => d[key])
.object(data);
//console.log(nestedData);
for (var k in nestedData) {
if (nestedData.hasOwnProperty(k) === true && filterlist.indexOf(k) < 0) {
delete nestedData[k];
}
}
var svg = chart.svg;
svg.selectAll('.' + chartId + 'lines').remove();
var lines = svg.selectAll('.' + chartId + 'lines')
.data(Object.keys(nestedData));
var lineg = lines.enter().append('g')
.attr('class', chartId + 'lines');
lineg.append('path')
.attr('class', 'line')
.attr('id', 'clientline')
//.attr('class', 'line')
.attr('stroke', function(d, i) {
return clientColorMap[d];
})
.attr('d', function(d) {
return chart.lineFunc(nestedData[d]);
});
lines.exit().remove();
} else {
chart.lineChart.attr('d', chart.lineFunc(data));
}
}
function showDisplayClientsHistory(clicked) {
var clientsHistoryDiv = document.getElementById('displaypanecontainer');
if (selectedDisplayClientIDList.length > 0) {
var yAxisFormatLoad = function(d) {
return (d * 100) + "%";
};
var yAxisFormatMemory = function(d) {
var mem = getNiceNumber(d * clients.systemMemoryMax);
return mem.number + mem.suffix;
};
var yAxisFormatDisplayMemory = function(d) {
var mem = getNiceNumber(d * clients.displayMemoryMax, true);
return mem.number + mem.suffix;
};
setupLineChart('displaycpuload', 'Client CPU Load', function(d) {
var cpu = d.cpuLoad;
return cpu.load / (cpu.load + cpu.idle);
}, yAxisFormatLoad, null, 0.5, true);
setupLineChart('displayclientload', 'SAGE2 Display Client Load', function(d) {
var client = d.clientLoad;
return client.cpuPercent / 100;
}, yAxisFormatLoad, null, 0.5, true);
setupLineChart('displaymemusage', 'Client System Memory', function(d) {
var mem = d.memUsage;
return mem.used / (mem.used + mem.free);
}, yAxisFormatMemory, null, 0.7, true);
setupLineChart('displayclientmem', 'SAGE2 Display Client Memory', function(d) {
var client = d.clientLoad;
return client.memResidentSet / clients.displayMemoryMax;
}, yAxisFormatDisplayMemory, null, 0.7, true);
updateLineChart('displaycpuload', clients.history, 'id', selectedDisplayClientIDList);
updateLineChart('displayclientload', clients.history, 'id', selectedDisplayClientIDList);
updateLineChart('displaymemusage', clients.history, 'id', selectedDisplayClientIDList);
updateLineChart('displayclientmem', clients.history, 'id', selectedDisplayClientIDList);
} else {
clientsHistoryDiv.style.height = 0 + "px";
}
if (clicked === true) {
if (selectedDisplayClientIDList.length > 0) {
window.scrollTo(0, document.body.scrollHeight);
clientsHistoryDiv.style.height = clientsHistoryDiv.scrollHeight + "px";
} else {
clientsHistoryDiv.style.height = 0 + "px";
}
}
}
function buttonClicked (d, i) {
var idx = selectedDisplayClientIDList.indexOf(d.id);
if (idx > -1) {
selectedDisplayClientIDList.splice(idx, 1);
d3.select(this.firstChild)
.attr('stroke', 'black');
} else {
selectedDisplayClientIDList.push(d.id);
d3.select(this.firstChild)
.attr('stroke', clientColorMap[d.id]);
}
showDisplayClientsHistory(true);
}
function cleanUpSelectedDisplayList () {
var currentList = clients.performanceMetrics;
if (currentList.length === 0) {
selectedDisplayClientIDList = [];
return;
}
for (var i = 0; i < selectedDisplayClientIDList.length; i++) {
var sdisplayid = selectedDisplayClientIDList[i];
var result = currentList.find(function(d) {
return d.id === sdisplayid;
});
if (result === null || result === undefined) {
selectedDisplayClientIDList.splice(i, 1);
}
}
}