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