blob: 8f2bc20f40b01c2e09c314fc6bd2ecd5becbe6a5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// size and spacing variables
var dotSpacing = 10; // spacing between centers of dots (radius)
var dotPadding = 0.5; // dot padding
var minDotRadius = 3; // min dot radius
var maxDotRadius = dotSpacing - dotPadding;
// arrays of information about each dot
var stats = {'servers': []};
var dots = new Array(0); // current sizes and change directions
var mousedDot = -1; // the dot currently under the mouse
var colorPalette = ['#0000CC', '#0014B8', '#0029A3', '#003D8F', '#00527A', '#006666', '#007A52', '#008F3D', '#00A329', '#00B814', '#00CC00', '#14D100', '#29D600', '#3DDB00', '#52E000', '#66E600', '#7AEB00', '#8FF000', '#A3F500', '#B8FA00', '#CCFF00', '#CCFF00', '#CCF200', '#CCE600', '#CCD900', '#CCCC00', '#CCBF00', '#CCB200', '#CCA600', '#CC9900', '#CC8C00', '#CC8000', '#CC7300', '#CC6600', '#CC5900', '#CC4C00', '#CC4000', '#CC3300', '#CC2600', '#CC1A00', '#CC0D00', '#CC0000'];
var nullColor = '#F5F8FA';
var deadColor = '#B000CC';
// animation variables
var drawing = false;
var canvas = document.getElementById('visCanvas');
var context = canvas.getContext('2d');
// mouse handling for server information display
document.getElementById('hoverable').addEventListener('mouseover', showId, false);
document.getElementById('hoverable').addEventListener('mousemove', showId, false);
document.getElementById('hoverable').addEventListener('mouseout', hideId, false);
document.getElementById('vishoverinfo').addEventListener('click', goToServer, false);
canvas.addEventListener('click', goToServer, false);
// initialize settings based on request parameters
var main = document.getElementById('main');
var speedStatType;
var colorStatType;
var speedDisabled = true;
var useCircles = true;
setShape(document.getElementById('shape'));
setSize(document.getElementById('size'));
setMotion(document.getElementById('motion'));
setColor(document.getElementById('color'));
// xml loading variables
var xmlReturned = true;
var xmlhttp=new XMLHttpRequest(); // don't bother allowing for IE 5 or 6 since canvas won't work
xmlhttp.onreadystatechange=function() {
handleNewData();
}
self.setInterval("getXML()",5000);
//self.setInterval("drawDots()",20);
window.requestAnimFrame = (function(callback){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
function handleNewData() {
if (xmlhttp.readyState!=4) {
return;
}
if (xmlhttp.status!=200 || xmlhttp.responseText==null) {
xmlReturned = true;
return;
}
var newstats = JSON.parse(xmlhttp.responseText);
for (var i in newstats.servers) {
for (var s in statNames) {
if (statNames[s])
continue;
newstats.servers[i][s] = Math.max(0,Math.floor(significance[s]*newstats.servers[i][s])/significance[s]);
if (adjustMax[s])
maxStatValues[s] = Math.max(newstats.servers[i][s],maxStatValues[s]);
}
}
initDerivedInfo(newstats);
var oldstats = stats;
while(drawing) {}
stats = newstats;
delete oldstats;
xmlReturned = true;
}
// set max and average
function setDerivedStats(serverStats) {
var avgStat = 0;
var maxStat = 0;
var maxIndex = 0;
for (var s in statNames) {
if (statNames[s])
continue;
normStat = serverStats[s]/maxStatValues[s];
if (normStat > 0)
avgStat += normStat;
if (maxStat < normStat) {
maxStat = normStat;
maxIndex = s;
}
}
serverStats.allmax = Math.floor(significance.allmax*Math.min(1,maxStat))/significance.allmax;
serverStats.allavg = Math.floor(significance.allavg*avgStat/numNormalStats)/significance.allavg;
serverStats.maxStat = maxIndex;
}
function initDerivedInfo(newstats) {
for (var i in newstats.servers) {
if ('dead' in newstats.servers[i] || 'bad' in newstats.servers[i]) {
continue;
}
if (i >= dots.length) {
dots.push({'size': maxDotRadius, 'growing': false});
}
setDerivedStats(newstats.servers[i]);
}
}
// construct server info for hover
function getInfo(serverStats) {
var extra = '<strong>' + serverStats.ip + '</strong>';
if ('dead' in serverStats || 'bad' in serverStats)
return extra;
extra = extra + ' (' + serverStats.hostname + ')';
var j = 0;
for (var s in statNames) {
if (j % 4 == 0)
extra = extra + '<br>\n';
extra = extra + '&nbsp;&nbsp;' + s + ': <strong>' + serverStats[s] + '</strong>';
j++;
}
extra = extra + ' (' + serverStats.maxStat + ')';
return extra;
}
// reload xml
function getXML() {
if (xmlReturned == true) {
xmlReturned = false;
xmlhttp.open('POST',jsonurl,true);
xmlhttp.send();
}
}
// redraw
function drawDots() {
requestAnimFrame(drawDots);
var width = Math.ceil(Math.sqrt(stats.servers.length));
var height = Math.ceil(stats.servers.length/width);
var x;
var y;
var server
var sizeChange;
drawing = true;
for (var i in stats.servers) {
server = stats.servers[i];
x = i % width;
y = Math.floor(i / width);
if ('bad' in server || 'dead' in server) {
strokeDot(x,y,maxDotRadius-1,deadColor);
continue;
}
if (speedDisabled || Math.floor(dots[i].size) > maxDotRadius) {
// check for resize by the user
dots[i].size = maxDotRadius;
} else if (server[speedStatType]<=0) {
// if not changing size, increase to max radius
if (dots[i].size < maxDotRadius)
dots[i].size = dots[i].size + 1;
if (dots[i].size > maxDotRadius)
dots[i].size = maxDotRadius;
} else {
sizeChange = getStat(i,speedStatType);
if (dots[i].growing) {
dots[i].size = dots[i].size + sizeChange;
if (dots[i].size + sizeChange > maxDotRadius) {
dots[i].growing = false;
}
}
else {
dots[i].size = dots[i].size - sizeChange;
if (dots[i].size - sizeChange < minDotRadius) {
dots[i].growing = true;
}
}
}
drawDot(x,y,Math.floor(dots[i].size),getColor(getStat(i,colorStatType)));
}
// mousedDot shouldn't be set to an invalid dot, but stats might have changed since then
if (mousedDot >= 0 && mousedDot < stats.servers.length)
document.getElementById('vishoverinfo').innerHTML=getInfo(stats.servers[mousedDot]);
drawing = false;
}
// fill in a few grey dots
function drawGrid() {
context.clearRect(0, 0, canvas.width, canvas.height);
for (var i=0, k=0; i < canvas.width; i+=dotSpacing*2,k++) {
for (var j=0, l=0; j < canvas.height; j+=dotSpacing*2,l++) {
drawDot(k,l,maxDotRadius,nullColor);
}
}
}
// fill a dot specified by indices into dot grid
function drawDot(i,j,r,c) {
var x = i*dotSpacing*2 + dotSpacing;
var y = j*dotSpacing*2 + dotSpacing;
context.clearRect(x-dotSpacing, y-dotSpacing, dotSpacing*2, dotSpacing*2);
if (useCircles)
fillCircle(x,y,r-dotPadding,c);
else
fillSquare(x-r,y-r,(r-dotPadding)*2,c);
}
// stroke a dot specified by indices into dot grid
function strokeDot(i,j,r,c) {
var x = i*dotSpacing*2 + dotSpacing;
var y = j*dotSpacing*2 + dotSpacing;
context.clearRect(x-dotSpacing, y-dotSpacing, dotSpacing*2, dotSpacing*2);
if (useCircles)
strokeCircle(x,y,r-dotPadding,c);
else
strokeSquare(x-r,y-r,(r-dotPadding)*2,c);
}
function getStat(dotIndex,statIndex) {
return Math.min(1,stats.servers[dotIndex][statIndex]/maxStatValues[statIndex]);
}
// translate color between 0 and maxObservedColor into an html color code
function getColor(normColor) {
return colorPalette[Math.round((colorPalette.length-1)*normColor)];
}
function strokeCircle(x,y,r,c) {
context.strokeStyle = c;
context.lineWidth = 2;
context.beginPath();
context.arc(x,y,r,0,Math.PI*2,true);
context.closePath();
context.stroke();
}
function fillCircle(x,y,r,c) {
context.fillStyle = c;
context.beginPath();
context.arc(x,y,r,0,Math.PI*2,true);
context.closePath();
context.fill();
}
function strokeSquare(x,y,d,c) {
context.strokeStyle = c;
context.lineWidth = 2;
context.strokeRect(x,y,d,d);
}
function fillSquare(x,y,d,c) {
context.fillStyle = c;
context.fillRect(x,y,d,d);
}
// callback for shape selection
function setShape(obj) {
switch (obj.selectedIndex) {
case 0:
useCircles = true;
break;
case 1:
useCircles = false;
break;
default:
useCircles = true;
}
drawGrid();
setState();
}
// callback for size selection
function setSize(obj) {
switch (obj.selectedIndex) {
case 0:
dotSpacing = 5;
minDotRadius = 1;
break;
case 1:
dotSpacing = 10;
minDotRadius = 3;
break;
case 2:
dotSpacing = 20;
minDotRadius = 5;
break;
case 3:
dotSpacing = 40;
minDotRadius = 7;
break;
default:
dotSpacing = 10;
minDotRadius = 3;
}
maxDotRadius = dotSpacing - dotPadding;
drawGrid();
setState();
}
// callback for motion selection
function setMotion(obj) {
if (obj.selectedIndex==0) {
speedDisabled = true;
setState();
return;
}
var i = 1;
for (var s in statNames) {
if (i==obj.selectedIndex) {
speedStatType = s;
break;
}
i++;
}
speedDisabled = false;
setState();
}
// callback for color selection
function setColor(obj) {
var i = 0;
for (var s in statNames) {
if (i==obj.selectedIndex) {
colorStatType = s;
break;
}
i++;
}
setState();
}
// hide server info on mouseout
function hideId(e) {
document.getElementById('vishoverinfo').style.visibility='hidden';
}
// display server info on mouseover
function showId(e) {
var x;
var y;
if (e.pageX || e.pageY) {
x = e.pageX + main.scrollLeft;
y = e.pageY + main.scrollTop;
}
else {
// clientX and clientY unimplemented
return;
}
var relx = x - canvas.offsetLeft - main.offsetLeft;
var rely = y - canvas.offsetTop - main.offsetTop;
var width = Math.ceil(Math.sqrt(stats.servers.length));
gotDot = Math.floor(relx/(dotSpacing*2)) + width*Math.floor(rely/(dotSpacing*2));
mousedDot = -1;
if (relx < (width*dotSpacing*2) && gotDot >= 0 && gotDot < stats.servers.length) {
mousedDot = gotDot;
document.getElementById('vishoverinfo').style.left=relx+canvas.offsetLeft;
document.getElementById('vishoverinfo').style.top=Math.max(0,rely+canvas.offsetTop-70);
document.getElementById('vishoverinfo').style.visibility='visible';
}
else {
document.getElementById('vishoverinfo').style.visibility='hidden';
}
}
function setState() {
var url = visurl+'?shape='+(useCircles?'circles':'squares')+'&size='+(dotSpacing*2)+(speedDisabled ? '' : '&motion='+speedStatType)+'&color='+colorStatType;
window.history.replaceState(window.history.state,'Server Activity',url);
}
// go to server page on click
function goToServer(e) {
// mousedDot shouldn't be set to an invalid dot, but stats might have changed since then
if (mousedDot >= 0 && mousedDot < stats.servers.length)
window.location = serverurl + stats.servers[mousedDot].ip;
}
window.onload = function() {
drawGrid();
drawDots();
getXML();
}