AzureCT/ServerSide/DisplayAvailability.js (374 lines of code) (raw):
// File: DisplayAvailability.js
// Start function when DOM has completely loaded
$(document).ready(function () {
// Open the AvailabilityHeader.xml file
$.get('AvailabilityHeader.xml', {}, function (xml) {
// Define Option Var
myHTMLOutput = '';
x = 0;
// Run the function for each Job in xml file
$('Job', xml).each(function (i) {
jobID = $(this).find('ID').text();
if (jobID != "") {
jobStart = $(this).find('StartTime').text();
var dStart = new Date(jobStart);
// Build Option Row for Drop down
myHTMLOutput += '<option value=' + jobID + '> ' + dateString(dStart, 'noMilli') + ' </option>';
x += 1;
};
});
// Update the Job List drop down with the HTML string
$("#JobList").append(myHTMLOutput);
// Select last job and call onchange event to load job details
document.getElementById("JobList").selectedIndex = x - 1;
document.getElementById("JobList").onchange();
});
});
function PullJobDetails() {
// Get the selected Job ID from the drop down
headerJobID = document.getElementById("JobList").value;
currentStartTime = '';
currentEndTime = '';
currentTarget = '';
currentTimeoutSeconds = '';
currentCallCount = '';
currentSuccessRate = '';
currentJobMin = '';
currentJobMax = '';
currentJobMedian = '';
currentJobDuration = '';
currentJobDisplay = '';
// Define graph var
var data = [];
// Set Data Loading Message to visible
document.getElementById('MessageDiv').style.visibility = 'visible';
// Open the AvailabilityHeader.xml file
$.get('AvailabilityHeader.xml', {}, function (xml) {
// Run the function for each Job in xml file
$('Job', xml).each(function (i) {
jobID = $(this).find('ID').text();
// Pull Job Header info into variables
if (jobID == headerJobID) {
currentStartTime = $(this).find('StartTime').text();
currentEndTime = $(this).find('EndTime').text();
currentTarget = $(this).find('Target').text();
currentTimeoutSeconds = $(this).find('TimeoutSeconds').text();
currentCallCount = $(this).find('CallCount').text();
currentSuccessRate = $(this).find('SuccessRate').text();
currentJobMin = $(this).find('JobMin').text();
currentJobMax = $(this).find('JobMax').text();
currentJobMedian = $(this).find('JobMedian').text();
var t1 = new Date(currentStartTime);
var t2 = new Date(currentEndTime);
var dif = (t2 - t1) / 1000;
var runDays = Math.floor(dif / 86400);
var rem = dif - (runDays * 86400);
var runHours = Math.floor(rem / 3600);
var rem = rem - (runHours * 3600);
var runMinutes = Math.floor(rem / 60);
var runSeconds = rem - (runMinutes * 60);
if ((+runDays) == (+1)) { var unitDays = 'Day'; } else { var unitDays = 'Days'; };
currentJobDuration = runDays + ' ' + unitDays + ', ' + pad(runHours) + ':' + pad(runMinutes) + ':' + pad(runSeconds) + ' (hh:mm:ss)';
myHTMLOutput = '';
myHTMLOutput += '<table id="SummaryTable">';
myHTMLOutput += '<tr><td class="SumHead" colspan=6 style="text-align:left;">Job Details:</td></tr>';
myHTMLOutput += '<tr>';
myHTMLOutput += '<td>Start Time:</td>';
myHTMLOutput += '<td>' + dateString(t1, 'noMilli') + '</td>';
myHTMLOutput += '<td>End Time:</td>';
myHTMLOutput += '<td>' + dateString(t2, 'noMilli') + '</td>';
myHTMLOutput += '<td>Job Duration:</td>';
myHTMLOutput += '<td>' + currentJobDuration + '</td>';
myHTMLOutput += '</tr>';
myHTMLOutput += '<tr>';
myHTMLOutput += '<td>Target IP:</td>';
myHTMLOutput += '<td>' + currentTarget + '</td>';
myHTMLOutput += '<td>Calls Sent:</td>';
myHTMLOutput += '<td>' + currentCallCount + ' (' + currentSuccessRate + '%) Successful</td>';
myHTMLOutput += '<td>Timeout Value:</td>';
myHTMLOutput += '<td>' + currentTimeoutSeconds + ' Seconds</td>';
myHTMLOutput += '</tr>';
myHTMLOutput += '<tr><td class="SumHead" colspan="6" style="text-align:left;">Results Summary:</td></tr>';
myHTMLOutput += '<tr>';
myHTMLOutput += '<td>Min:</td>';
myHTMLOutput += '<td>' + currentJobMin + ' ms</td>';
myHTMLOutput += '<td>Max:</td>';
myHTMLOutput += '<td>' + currentJobMax + ' ms</td>';
myHTMLOutput += '<td>Median:</td>';
myHTMLOutput += '<td>' + currentJobMedian + ' ms</td>';
myHTMLOutput += '</tr>';
myHTMLOutput += '</table>';
$("#SummaryDiv").html(myHTMLOutput);
};
});
});
// Open the AvailabilityDetail.xml file
$.get('AvailabilityDetail.xml', {}, function (xml) {
// Define HTML string Var
myHTMLOutput = '';
myHTMLOutput += '<table id="ResultsTable"';
myHTMLOutput += '<thead>';
myHTMLOutput += '<th id=time>Time Stamp</th>'
myHTMLOutput += '<th id=display>Remarks</th>'
myHTMLOutput += '<th id=duration>Duration</th>'
myHTMLOutput += '<th id=trace>Trace</th>'
myHTMLOutput += '</thead>';
var t1 = new Date(currentStartTime);
var t2 = new Date(currentEndTime);
var chartWidth = document.getElementById("ResultsGraph").clientWidth - 100;
var totalDurationMS = (t2 - t1);
var timePerPixel = Math.round(totalDurationMS / chartWidth);
var iRunning = 0;
// Run the function for each Job in xml file
$('JobRecord', xml).each(function (i) {
jobID = $(this).find('JobID').text();
if (jobID == headerJobID) {
callTimeStamp = $(this).find('TimeStamp').text();
callID = $(this).find('CallID').text();
callReturn = $(this).find('Return').text();
callDisplay = $(this).find('Display').text();
callValid = $(this).find('Valid').text();
callDuration = $(this).find('Duration').text();
callTagged = $(this).find('Tag').text();
// Calculate X position
var thisTime = new Date(callTimeStamp);
if (callID == 1 || callID == '') {
dif = 1;
pos = 1;
lastTime = thisTime;
}
else {
dif = thisTime - lastTime;
pos += dif / timePerPixel;
lastTime = thisTime;
};
// Build Table Row for Results Table
myHTMLOutput += '<tr>';
myHTMLOutput += '<td>' + dateString(thisTime, 'withMilli') + '</td>';
myHTMLOutput += '<td>' + callDisplay + '</td>';
myHTMLOutput += '<td>' + Math.round(callDuration) + ' ms</td>';
if (callTagged == 'True') {
myHTMLOutput += "<td><a href=javascript:showTrace('" + jobID + "','" + callID + "')>View Trace</a></td>";
}
else {
myHTMLOutput += '<td> </td>';
};
myHTMLOutput += '</tr>';
// Build graph array
var myCallID = (+callID);
var datapoint = { x: pos, y: Math.round(callDuration), z: callValid };
data.push(datapoint);
};
});
myHTMLOutput += "</table>";
// Update the DIV called ResultsDiv with the HTML string
$("#ResultsDiv").html(myHTMLOutput);
var myTick;
if (currentCallCount < 15) { myTick = 1; } else { myTick = currentCallCount / 15; };
var myYMax;
if (currentJobMax > 500) { myYMax = currentJobMax; } else { myYMax = 500; };
// Instanitate the graph
ClearChart("ResultsGraph");
var myLineChart = new LineChart({
canvasId: "ResultsGraph",
minX: 0,
minY: 0,
maxX: currentCallCount,
maxY: myYMax,
unitsPerTickX: myTick,
unitsPerTickY: myYMax / 8
});
// Draw the chart
myLineChart.drawLine(data, "blue", 1);
});
// Set Data Loading Message to visible
document.getElementById('MessageDiv').style.visibility = 'hidden';
};
function showTrace(JobID, CallID) {
// Open the AvailabilityHeader.xml file
$.get('AvailabilityTrace.xml', {}, function (xml) {
// Define HTML string Var
myHTMLOutput = '<span class="b">Select Trace Route</span>';
myHTMLOutput += '<table id="TraceTable">';
myHTMLOutput += '<tr>';
myHTMLOutput += '<th id="HopID">Hop #</th>';
myHTMLOutput += '<th id="Address">IP Address</th>';
myHTMLOutput += '<th id="Latency">Latency</th>';
myHTMLOutput += '</tr>';
// Run the function for each Trace in xml file
$('TraceRecord', xml).each(function (i) {
traceJobID = $(this).find('JobID').text();
traceCallID = $(this).find('CallID').text();
// Pull Job Header info into variables
if (traceJobID == JobID && traceCallID == CallID) {
traceTimeStamp = $(this).find('TimeStamp').text();
traceHopID = $(this).find('HopID').text();
traceAddress = $(this).find('Address').text();
traceTripTime = $(this).find('TripTime').text();
myHTMLOutput += '<tr>';
myHTMLOutput += '<td>' + traceHopID + '</td>';
myHTMLOutput += '<td>' + traceAddress + '</td>';
switch (traceTripTime) {
case '*':
myHTMLOutput += '<td>*</td>';
break;
case '0':
myHTMLOutput += '<td>< 1 ms</td>';
break;
default:
myHTMLOutput += '<td>' + traceTripTime + ' ms</td>';
};
myHTMLOutput += '</tr>';
};
});
myHTMLOutput += '</table>';
myHTMLOutput += '<br />';
myHTMLOutput += '';
myHTMLOutput += '<button style="position:absolute; right:45%;" onclick="closeTrace();">Close</button>';
myHTMLOutput += '<br />';
myHTMLOutput += '<br />';
myHTMLOutput += '<span class="b">Notes:</span><br />';
myHTMLOutput += '<span">The maximum trace route time will normally be less than the Duration value on the main web page. This is because the trace route is only calculating network latency, whereas the Duration column includes the processing time of the IIS server.</span>';
myHTMLOutput += '<br /><br />';
myHTMLOutput += '<span">This trace route was started <span class="b">*after*</span> the associated web call was completed. Network conditions may have changed in the short time span between the Web Call and the Trace Route. Also, each trace route row is a separate network trace, network conditions can vary between each trace event as well.</span>';
myHTMLOutput += '<br />';
myHTMLOutput += '<br />';
$("#TraceDiv").html(myHTMLOutput);
document.getElementById('TraceDiv').style.visibility = 'visible';
});
};
function closeTrace() {
document.getElementById('TraceDiv').style.visibility = 'hidden';
};
function pad(num) {
return ('000' + num).substr(-2);
};
function dateString(dDate, sOption) {
var yyyy = dDate.getFullYear().toString();
var mm = (dDate.getMonth() + 1).toString();
var dd = dDate.getDate().toString();
var hh = dDate.getHours().toString();
var MM = dDate.getMinutes().toString();
var ss = dDate.getSeconds().toString();
switch (sOption) {
case 'noMilli':
var sDisplay = yyyy + '-' + (mm[1] ? mm : "0" + mm[0]) + '-' + (dd[1] ? dd : "0" + dd[0]) + ' | ' + (hh[1] ? hh : "0" + hh[0]) + ':' + (MM[1] ? MM : "0" + MM[0]) + ':' + (ss[1] ? ss : "0" + ss[0]);
break;
case 'withMilli':
var fff = ('000' + dDate.getMilliseconds().toString()).substr(-3);
var sDisplay = yyyy + '-' + (mm[1] ? mm : "0" + mm[0]) + '-' + (dd[1] ? dd : "0" + dd[0]) + ' | ' + (hh[1] ? hh : "0" + hh[0]) + ':' + (MM[1] ? MM : "0" + MM[0]) + ':' + (ss[1] ? ss : "0" + ss[0]) + '.' + fff;
break;
default:
var sDisplay = '';
};
return sDisplay;
};
function LineChart(con) {
// user defined properties
this.canvas = document.getElementById(con.canvasId);
this.minX = con.minX;
this.minY = con.minY;
this.maxX = con.maxX;
this.maxY = con.maxY;
this.unitsPerTickX = con.unitsPerTickX;
this.unitsPerTickY = con.unitsPerTickY;
// constants
this.padding = 10;
this.tickSize = 5;
this.axisColor = "#555";
this.pointRadius = 3;
this.font = "12pt Calibri";
this.fontHeight = 12;
this.xAxisAdjustment = 15;
this.yAxisAdjustment = 11;
// relationships
this.context = this.canvas.getContext("2d");
this.rangeX = this.maxX - this.minX;
this.rangeY = this.maxY - this.minY;
this.numXTicks = Math.round(this.rangeX / this.unitsPerTickX);
this.numYTicks = Math.round(this.rangeY / this.unitsPerTickY);
this.x = this.getLongestValueWidth() + this.padding * 2;
this.y = this.padding * 2;
this.width = (this.canvas.width - this.x - this.padding * 2) - this.yAxisAdjustment;
this.height = this.canvas.height - this.y - this.padding - this.fontHeight - this.xAxisAdjustment;
this.scaleX = this.width / this.rangeX;
this.scaleY = this.height / this.rangeY;
// draw title and axis labels
this.context.textAlign = 'center';
var ChartLabel = 'Graph of Call Response Time';
this.context.fillText(ChartLabel, (this.width / 2) + 50, 20);
var xLabel = 'Time (duration of this data set)';
this.context.fillText(xLabel, (this.width / 2) + 50, 285);
var yLabel = 'Response Time (ms)';
this.context.save();
this.context.rotate(-Math.PI / 2);
this.context.fillText(yLabel, -150, 18);
this.context.restore();
// draw x y axis and tick marks
this.drawXAxis();
this.drawYAxis();
};
function ClearChart(CanID) {
var canvas = document.getElementById(CanID);
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
};
LineChart.prototype.getLongestValueWidth = function () {
this.context.font = this.font;
var longestValueWidth = 0;
for (var n = 0; n <= this.numYTicks; n++) {
var value = this.maxY - (n * this.unitsPerTickY);
longestValueWidth = Math.max(longestValueWidth, this.context.measureText(value).width);
}
return longestValueWidth;
};
LineChart.prototype.drawXAxis = function () {
var context = this.context;
context.save();
context.beginPath();
context.moveTo(this.x + this.yAxisAdjustment, this.y + this.height);
context.lineTo(this.x + this.width + this.yAxisAdjustment, this.y + this.height);
context.strokeStyle = this.axisColor;
context.lineWidth = 2;
context.stroke();
// draw tick marks
//for (var n = 0; n < this.numXTicks; n++) {
// context.beginPath();
// context.moveTo((n + 1) * this.width / this.numXTicks + this.x, this.y + this.height);
// context.lineTo((n + 1) * this.width / this.numXTicks + this.x, this.y + this.height - this.tickSize);
// context.stroke();
//}
// draw labels
context.font = this.font;
context.fillStyle = "black";
context.textAlign = "center";
context.textBaseline = "middle";
//for (var n = 0; n < this.numXTicks; n++) {
// var label = Math.round((n + 1) * this.maxX / this.numXTicks);
// context.save();
// context.translate((n + 1) * this.width / this.numXTicks + this.x, this.y + this.height + this.padding);
// context.fillText(label, 0, 0);
// context.restore();
//}
context.restore();
};
LineChart.prototype.drawYAxis = function () {
var context = this.context;
context.save();
context.save();
context.beginPath();
context.moveTo(this.x + this.yAxisAdjustment, this.y);
context.lineTo(this.x + this.yAxisAdjustment, this.y + this.height);
context.strokeStyle = this.axisColor;
context.lineWidth = 2;
context.stroke();
context.restore();
// draw tick marks
for (var n = 0; n < this.numYTicks; n++) {
context.beginPath();
context.moveTo(this.x + this.yAxisAdjustment, n * this.height / this.numYTicks + this.y);
context.lineTo(this.x + this.tickSize + this.yAxisAdjustment, n * this.height / this.numYTicks + this.y);
context.stroke();
}
// draw values
context.font = this.font;
context.fillStyle = "black";
context.textAlign = "right";
context.textBaseline = "middle";
for (var n = 0; n < this.numYTicks; n++) {
var value = Math.round(this.maxY - n * this.maxY / this.numYTicks);
context.save();
context.translate(this.x - this.padding + this.yAxisAdjustment, n * this.height / this.numYTicks + this.y);
context.fillText(value, 0, 0);
context.restore();
}
context.restore();
};
LineChart.prototype.drawLine = function (data, color, width) {
var context = this.context;
context.save();
this.transformContext();
context.lineWidth = width;
context.strokeStyle = color;
context.fillStyle = color;
context.beginPath();
context.moveTo(data[0].x + 20, data[0].y * this.scaleY);
for (var n = 0; n < data.length; n++) {
var point = data[n];
// draw segment
context.lineTo(point.x + 20, point.y * this.scaleY);
context.stroke();
context.closePath();
context.beginPath();
if (data[n].z == "False") { context.fillStyle = "red"; };
context.arc(point.x + 20, point.y * this.scaleY, this.pointRadius, 0, 2 * Math.PI, false);
context.fill();
context.closePath();
if (data[n].z == "False") { context.fillStyle = color; };
// position for next segment
context.beginPath();
context.moveTo(point.x + 20, point.y * this.scaleY);
}
context.restore();
};
LineChart.prototype.transformContext = function () {
var context = this.context;
// move context to center of canvas
this.context.translate(this.x, this.y + this.height);
// invert the y scale so that that increments
// as you move upwards
context.scale(1, -1);
};