Make Hystrix dash embeddable

This commit is contained in:
Dave Syer
2014-09-02 12:32:08 +01:00
parent 4ca9d5f772
commit 34cbf7a64b
26 changed files with 2114 additions and 2 deletions

View File

@@ -6,7 +6,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.cloud.netflix.hystrix.HystrixDashboardConfiguration;
import org.springframework.cloud.netflix.hystrix.dashboard.HystrixDashboardConfiguration;
import org.springframework.context.annotation.Import;
/**

View File

@@ -1,4 +1,4 @@
package org.springframework.cloud.netflix.hystrix;
package org.springframework.cloud.netflix.hystrix.dashboard;
import java.io.IOException;
import java.io.InputStream;

View File

@@ -0,0 +1,196 @@
.dependencies .spacer {
width: 100%;
margin: 0 auto;
padding-top:4px;
clear:both;
}
.dependencies .last {
margin-right: 0px;
}
.dependencies span.loading {
display: block;
padding-top: 6%;
padding-bottom: 6%;
color: gray;
text-align: center;
}
.dependencies span.loading.failed {
color: red;
}
.dependencies div.monitor {
float: left;
margin-right:5px;
margin-top:5px;
}
.dependencies div.monitor p.name {
font-weight:bold;
font-size: 10pt;
text-align: right;
padding-bottom: 5px;
}
.dependencies div.monitor_data {
margin: 0 auto;
}
/* override the HREF when we have specified it as a tooltip to not act like a link */
.dependencies div.monitor_data a.tooltip {
text-decoration: none;
cursor: default;
}
.dependencies div.monitor_data div.counters {
text-align: right;
padding-bottom: 10px;
font-size: 10pt;
clear: both;
}
.dependencies div.monitor_data div.counters div.cell {
display: inline;
float: right;
}
.dependencies .borderRight {
border-right: 1px solid grey;
padding-right: 6px;
padding-left: 8px;
}
.dependencies div.cell .line {
display: block;
}
.dependencies div.monitor_data a,
.dependencies span.rate_value {
font-weight:bold;
}
.dependencies span.smaller {
font-size: 8pt;
color: grey;
}
.dependencies div.tableRow {
width:100%;
white-space: nowrap;
font-size: 8pt;
margin: 0 auto;
clear:both;
padding-left:26%;
}
.dependencies div.tableRow .cell {
float:left;
}
.dependencies div.tableRow .header {
width:18%;
text-align:right;
padding-right:2%;
}
.dependencies div.tableRow .data {
width:17%;
font-weight: bold;
text-align:right;
}
.dependencies div.monitor {
width: 245px; /* we want a fixed width instead of percentage as I want the boxes to be a set size and then fill in as many as can fit in each row ... this allows 3 columns on an iPad */
height: 150px;
}
.dependencies .success {
color: green;
}
.dependencies .shortCircuited {
color: blue;
}
.dependencies .timeout {
color: #FF9900; /* shade of orange */
}
.dependencies .failure {
color: red;
}
.dependencies .rejected {
color: purple;
}
.dependencies .exceptionsThrown {
color: brown;
}
.dependencies div.monitor_data a.rate {
color: black;
font-size: 11pt;
}
.dependencies div.rate {
padding-top: 1px;
clear:both;
text-align:right;
}
.dependencies .errorPercentage {
color: grey;
}
.dependencies div.cell .errorPercentage {
padding-left:5px;
font-size: 12pt !important;
}
.dependencies div.monitor div.chart {
}
.dependencies div.monitor div.chart svg {
}
.dependencies div.monitor div.chart svg text {
fill: white;
}
.dependencies div.circuitStatus {
width:100%;
white-space: nowrap;
font-size: 9pt;
margin: 0 auto;
clear:both;
text-align:right;
padding-top: 4px;
}
.dependencies #hidden {
width:1px;
height:1px;
background: lightgrey;
display: none;
}
/* sparkline */
.dependencies path {
stroke: steelblue;
stroke-width: 1;
fill: none;
}
}

View File

@@ -0,0 +1,536 @@
(function(window) {
// cache the templates we use on this page as global variables (asynchronously)
jQuery.get(getRelativePath("../components/hystrixCommand/templates/hystrixCircuit.html"), function(data) {
hystrixTemplateCircuit = data;
});
jQuery.get(getRelativePath("../components/hystrixCommand/templates/hystrixCircuitContainer.html"), function(data) {
hystrixTemplateCircuitContainer = data;
});
function getRelativePath(path) {
var p = location.pathname.slice(0, location.pathname.lastIndexOf("/")+1);
return p + path;
}
/**
* Object containing functions for displaying and updating the UI with streaming data.
*
* Publish this externally as "HystrixCommandMonitor"
*/
window.HystrixCommandMonitor = function(containerId, args) {
var self = this; // keep scope under control
self.args = args;
if(self.args == undefined) {
self.args = {};
}
this.containerId = containerId;
/**
* Initialization on construction
*/
// intialize various variables we use for visualization
var maxXaxisForCircle="40%";
var maxYaxisForCircle="40%";
var maxRadiusForCircle="125";
// CIRCUIT_BREAKER circle visualization settings
self.circuitCircleRadius = d3.scale.pow().exponent(0.5).domain([0, 400]).range(["5", maxRadiusForCircle]); // requests per second per host
self.circuitCircleYaxis = d3.scale.linear().domain([0, 400]).range(["30%", maxXaxisForCircle]);
self.circuitCircleXaxis = d3.scale.linear().domain([0, 400]).range(["30%", maxYaxisForCircle]);
self.circuitColorRange = d3.scale.linear().domain([10, 25, 40, 50]).range(["green", "#FFCC00", "#FF9900", "red"]);
self.circuitErrorPercentageColorRange = d3.scale.linear().domain([0, 10, 35, 50]).range(["grey", "black", "#FF9900", "red"]);
/**
* We want to keep sorting in the background since data values are always changing, so this will re-sort every X milliseconds
* to maintain whatever sort the user (or default) has chosen.
*
* In other words, sorting only for adds/deletes is not sufficient as all but alphabetical sort are dynamically changing.
*/
setInterval(function() {
// sort since we have added a new one
self.sortSameAsLast();
}, 10000);
/**
* END of Initialization on construction
*/
/**
* Event listener to handle new messages from EventSource as streamed from the server.
*/
/* public */ self.eventSourceMessageListener = function(e) {
var data = JSON.parse(e.data);
if(data) {
// check for reportingHosts (if not there, set it to 1 for singleHost vs cluster)
if(!data.reportingHosts) {
data.reportingHosts = 1;
}
if(data && data.type == 'HystrixCommand') {
if (data.deleteData == 'true') {
deleteCircuit(data.escapedName);
} else {
displayCircuit(data);
}
}
}
};
/**
* Pre process the data before displying in the UI.
* e.g Get Averages from sums, do rate calculation etc.
*/
function preProcessData(data) {
validateData(data);
// escape string used in jQuery & d3 selectors
data.escapedName = data.name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\$1');
// do math
converAllAvg(data);
calcRatePerSecond(data);
}
/**
* Since the stream of data can be aggregated from multiple hosts in a tiered manner
* the aggregation just sums everything together and provides us the denominator (reportingHosts)
* so we must divide by it to get an average per instance value.
*
* We want to do this on any numerical values where we want per instance rather than cluster-wide sum.
*/
function converAllAvg(data) {
convertAvg(data, "errorPercentage", true);
convertAvg(data, "latencyExecute_mean", false);
convertAvg(data, "latencyTotal_mean", false);
// the following will break when it becomes a compound string if the property is dynamically changed
convertAvg(data, "propertyValue_metricsRollingStatisticalWindowInMilliseconds", false);
}
function convertAvg(data, key, decimal) {
if (decimal) {
data[key] = getInstanceAverage(data[key], data["reportingHosts"], decimal);
} else {
data[key] = getInstanceAverage(data[key], data["reportingHosts"], decimal);
}
}
function getInstanceAverage(value, reportingHosts, decimal) {
if (decimal) {
return roundNumber(value/reportingHosts);
} else {
return Math.floor(value/reportingHosts);
}
}
function calcRatePerSecond(data) {
var numberSeconds = data["propertyValue_metricsRollingStatisticalWindowInMilliseconds"] / 1000;
var totalRequests = data["requestCount"];
if (totalRequests < 0) {
totalRequests = 0;
}
data["ratePerSecond"] = roundNumber(totalRequests / numberSeconds);
data["ratePerSecondPerHost"] = roundNumber(totalRequests / numberSeconds / data["reportingHosts"]) ;
}
function validateData(data) {
assertNotNull(data,"reportingHosts");
assertNotNull(data,"type");
assertNotNull(data,"name");
assertNotNull(data,"group");
// assertNotNull(data,"currentTime");
assertNotNull(data,"isCircuitBreakerOpen");
assertNotNull(data,"errorPercentage");
assertNotNull(data,"errorCount");
assertNotNull(data,"requestCount");
assertNotNull(data,"rollingCountCollapsedRequests");
assertNotNull(data,"rollingCountExceptionsThrown");
assertNotNull(data,"rollingCountFailure");
assertNotNull(data,"rollingCountFallbackFailure");
assertNotNull(data,"rollingCountFallbackRejection");
assertNotNull(data,"rollingCountFallbackSuccess");
assertNotNull(data,"rollingCountResponsesFromCache");
assertNotNull(data,"rollingCountSemaphoreRejected");
assertNotNull(data,"rollingCountShortCircuited");
assertNotNull(data,"rollingCountSuccess");
assertNotNull(data,"rollingCountThreadPoolRejected");
assertNotNull(data,"rollingCountTimeout");
assertNotNull(data,"currentConcurrentExecutionCount");
assertNotNull(data,"latencyExecute_mean");
assertNotNull(data,"latencyExecute");
assertNotNull(data,"latencyTotal_mean");
assertNotNull(data,"latencyTotal");
assertNotNull(data,"propertyValue_circuitBreakerRequestVolumeThreshold");
assertNotNull(data,"propertyValue_circuitBreakerSleepWindowInMilliseconds");
assertNotNull(data,"propertyValue_circuitBreakerErrorThresholdPercentage");
assertNotNull(data,"propertyValue_circuitBreakerForceOpen");
assertNotNull(data,"propertyValue_executionIsolationStrategy");
assertNotNull(data,"propertyValue_executionIsolationThreadTimeoutInMilliseconds");
assertNotNull(data,"propertyValue_executionIsolationThreadInterruptOnTimeout");
// assertNotNull(data,"propertyValue_executionIsolationThreadPoolKeyOverride");
assertNotNull(data,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests");
assertNotNull(data,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests");
assertNotNull(data,"propertyValue_requestCacheEnabled");
assertNotNull(data,"propertyValue_requestLogEnabled");
assertNotNull(data,"propertyValue_metricsRollingStatisticalWindowInMilliseconds");
}
function assertNotNull(data, key) {
if(data[key] == undefined) {
throw new Error("Key Missing: " + key + " for " + data.name);
}
}
/**
* Method to display the CIRCUIT data
*
* @param data
*/
/* private */ function displayCircuit(data) {
try {
preProcessData(data);
} catch (err) {
log("Failed preProcessData: " + err.message);
return;
}
// add the 'addCommas' function to the 'data' object so the HTML templates can use it
data.addCommas = addCommas;
// add the 'roundNumber' function to the 'data' object so the HTML templates can use it
data.roundNumber = roundNumber;
// add the 'getInstanceAverage' function to the 'data' object so the HTML templates can use it
data.getInstanceAverage = getInstanceAverage;
var addNew = false;
// check if we need to create the container
if(!$('#CIRCUIT_' + data.escapedName).length) {
// args for display
if(self.args.includeDetailIcon != undefined && self.args.includeDetailIcon) {
data.includeDetailIcon = true;
}else {
data.includeDetailIcon = false;
}
// it doesn't exist so add it
var html = tmpl(hystrixTemplateCircuitContainer, data);
// remove the loading thing first
$('#' + containerId + ' span.loading').remove();
// now create the new data and add it
$('#' + containerId + '').append(html);
// add the default sparkline graph
d3.selectAll('#graph_CIRCUIT_' + data.escapedName + ' svg').append("svg:path");
// remember this is new so we can trigger a sort after setting data
addNew = true;
}
// now update/insert the data
$('#CIRCUIT_' + data.escapedName + ' div.monitor_data').html(tmpl(hystrixTemplateCircuit, data));
var ratePerSecond = data.ratePerSecond;
var ratePerSecondPerHost = data.ratePerSecondPerHost;
var ratePerSecondPerHostDisplay = ratePerSecondPerHost;
var errorThenVolume = (data.errorPercentage * 100000000) + ratePerSecond;
// set the rates on the div element so it's available for sorting
$('#CIRCUIT_' + data.escapedName).attr('rate_value', ratePerSecond);
$('#CIRCUIT_' + data.escapedName).attr('error_then_volume', errorThenVolume);
// update errorPercentage color on page
$('#CIRCUIT_' + data.escapedName + ' a.errorPercentage').css('color', self.circuitErrorPercentageColorRange(data.errorPercentage));
updateCircle('circuit', '#CIRCUIT_' + data.escapedName + ' circle', ratePerSecondPerHostDisplay, data.errorPercentage);
if(data.graphValues) {
// we have a set of values to initialize with
updateSparkline('circuit', '#CIRCUIT_' + data.escapedName + ' path', data.graphValues);
} else {
updateSparkline('circuit', '#CIRCUIT_' + data.escapedName + ' path', ratePerSecond);
}
if(addNew) {
// sort since we added a new circuit
self.sortSameAsLast();
}
}
/* round a number to X digits: num => the number to round, dec => the number of decimals */
/* private */ function roundNumber(num) {
var dec=1;
var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
var resultAsString = result.toString();
if(resultAsString.indexOf('.') == -1) {
resultAsString = resultAsString + '.0';
}
return resultAsString;
};
/* private */ function updateCircle(variablePrefix, cssTarget, rate, errorPercentage) {
var newXaxisForCircle = self[variablePrefix + 'CircleXaxis'](rate);
if(parseInt(newXaxisForCircle) > parseInt(maxXaxisForCircle)) {
newXaxisForCircle = maxXaxisForCircle;
}
var newYaxisForCircle = self[variablePrefix + 'CircleYaxis'](rate);
if(parseInt(newYaxisForCircle) > parseInt(maxYaxisForCircle)) {
newYaxisForCircle = maxYaxisForCircle;
}
var newRadiusForCircle = self[variablePrefix + 'CircleRadius'](rate);
if(parseInt(newRadiusForCircle) > parseInt(maxRadiusForCircle)) {
newRadiusForCircle = maxRadiusForCircle;
}
d3.selectAll(cssTarget)
.transition()
.duration(400)
.attr("cy", newYaxisForCircle)
.attr("cx", newXaxisForCircle)
.attr("r", newRadiusForCircle)
.style("fill", self[variablePrefix + 'ColorRange'](errorPercentage));
}
/* private */ function updateSparkline(variablePrefix, cssTarget, newDataPoint) {
var currentTimeMilliseconds = new Date().getTime();
var data = self[variablePrefix + cssTarget + '_data'];
if(typeof data == 'undefined') {
// else it's new
if(typeof newDataPoint == 'object') {
// we received an array of values, so initialize with it
data = newDataPoint;
} else {
// v: VALUE, t: TIME_IN_MILLISECONDS
data = [{"v":parseFloat(newDataPoint),"t":currentTimeMilliseconds}];
}
self[variablePrefix + cssTarget + '_data'] = data;
} else {
if(typeof newDataPoint == 'object') {
/* if an array is passed in we'll replace the cached one */
data = newDataPoint;
} else {
// else we just add to the existing one
data.push({"v":parseFloat(newDataPoint),"t":currentTimeMilliseconds});
}
}
while(data.length > 200) { // 400 should be plenty for the 2 minutes we have the scale set to below even with a very low update latency
// remove data so we don't keep increasing forever
data.shift();
}
if(data.length == 1 && data[0].v == 0) {
//console.log("we have a single 0 so skipping");
// don't show if we have a single 0
return;
}
if(data.length > 1 && data[0].v == 0 && data[1].v != 0) {
//console.log("we have a leading 0 so removing it");
// get rid of a leading 0 if the following number is not a 0
data.shift();
}
var xScale = d3.time.scale().domain([new Date(currentTimeMilliseconds-(60*1000*2)), new Date(currentTimeMilliseconds)]).range([0, 140]);
var yMin = d3.min(data, function(d) { return d.v; });
var yMax = d3.max(data, function(d) { return d.v; });
var yScale = d3.scale.linear().domain([yMin, yMax]).nice().range([60, 0]); // y goes DOWN, so 60 is the "lowest"
sparkline = d3.svg.line()
// assign the X function to plot our line as we wish
.x(function(d,i) {
// return the X coordinate where we want to plot this datapoint based on the time
return xScale(new Date(d.t));
})
.y(function(d) {
return yScale(d.v);
})
.interpolate("basis");
d3.selectAll(cssTarget).attr("d", sparkline(data));
}
/* private */ function deleteCircuit(circuitName) {
$('#CIRCUIT_' + circuitName).remove();
}
};
// public methods for sorting
HystrixCommandMonitor.prototype.sortByVolume = function() {
var direction = "desc";
if(this.sortedBy == 'rate_desc') {
direction = 'asc';
}
this.sortByVolumeInDirection(direction);
};
HystrixCommandMonitor.prototype.sortByVolumeInDirection = function(direction) {
this.sortedBy = 'rate_' + direction;
$('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'rate_value'});
};
HystrixCommandMonitor.prototype.sortAlphabetically = function() {
var direction = "asc";
if(this.sortedBy == 'alph_asc') {
direction = 'desc';
}
this.sortAlphabeticalInDirection(direction);
};
HystrixCommandMonitor.prototype.sortAlphabeticalInDirection = function(direction) {
this.sortedBy = 'alph_' + direction;
$('#' + this.containerId + ' div.monitor').tsort("p.name", {order: direction});
};
HystrixCommandMonitor.prototype.sortByError = function() {
var direction = "desc";
if(this.sortedBy == 'error_desc') {
direction = 'asc';
}
this.sortByErrorInDirection(direction);
};
HystrixCommandMonitor.prototype.sortByErrorInDirection = function(direction) {
this.sortedBy = 'error_' + direction;
$('#' + this.containerId + ' div.monitor').tsort(".errorPercentage .value", {order: direction});
};
HystrixCommandMonitor.prototype.sortByErrorThenVolume = function() {
var direction = "desc";
if(this.sortedBy == 'error_then_volume_desc') {
direction = 'asc';
}
this.sortByErrorThenVolumeInDirection(direction);
};
HystrixCommandMonitor.prototype.sortByErrorThenVolumeInDirection = function(direction) {
this.sortedBy = 'error_then_volume_' + direction;
$('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'error_then_volume'});
};
HystrixCommandMonitor.prototype.sortByLatency90 = function() {
var direction = "desc";
if(this.sortedBy == 'lat90_desc') {
direction = 'asc';
}
this.sortedBy = 'lat90_' + direction;
this.sortByMetricInDirection(direction, ".latency90 .value");
};
HystrixCommandMonitor.prototype.sortByLatency99 = function() {
var direction = "desc";
if(this.sortedBy == 'lat99_desc') {
direction = 'asc';
}
this.sortedBy = 'lat99_' + direction;
this.sortByMetricInDirection(direction, ".latency99 .value");
};
HystrixCommandMonitor.prototype.sortByLatency995 = function() {
var direction = "desc";
if(this.sortedBy == 'lat995_desc') {
direction = 'asc';
}
this.sortedBy = 'lat995_' + direction;
this.sortByMetricInDirection(direction, ".latency995 .value");
};
HystrixCommandMonitor.prototype.sortByLatencyMean = function() {
var direction = "desc";
if(this.sortedBy == 'latMean_desc') {
direction = 'asc';
}
this.sortedBy = 'latMean_' + direction;
this.sortByMetricInDirection(direction, ".latencyMean .value");
};
HystrixCommandMonitor.prototype.sortByLatencyMedian = function() {
var direction = "desc";
if(this.sortedBy == 'latMedian_desc') {
direction = 'asc';
}
this.sortedBy = 'latMedian_' + direction;
this.sortByMetricInDirection(direction, ".latencyMedian .value");
};
HystrixCommandMonitor.prototype.sortByMetricInDirection = function(direction, metric) {
$('#' + this.containerId + ' div.monitor').tsort(metric, {order: direction});
};
// this method is for when new divs are added to cause the elements to be sorted to whatever the user last chose
HystrixCommandMonitor.prototype.sortSameAsLast = function() {
if(this.sortedBy == 'alph_asc') {
this.sortAlphabeticalInDirection('asc');
} else if(this.sortedBy == 'alph_desc') {
this.sortAlphabeticalInDirection('desc');
} else if(this.sortedBy == 'rate_asc') {
this.sortByVolumeInDirection('asc');
} else if(this.sortedBy == 'rate_desc') {
this.sortByVolumeInDirection('desc');
} else if(this.sortedBy == 'error_asc') {
this.sortByErrorInDirection('asc');
} else if(this.sortedBy == 'error_desc') {
this.sortByErrorInDirection('desc');
} else if(this.sortedBy == 'error_then_volume_asc') {
this.sortByErrorThenVolumeInDirection('asc');
} else if(this.sortedBy == 'error_then_volume_desc') {
this.sortByErrorThenVolumeInDirection('desc');
} else if(this.sortedBy == 'lat90_asc') {
this.sortByMetricInDirection('asc', '.latency90 .value');
} else if(this.sortedBy == 'lat90_desc') {
this.sortByMetricInDirection('desc', '.latency90 .value');
} else if(this.sortedBy == 'lat99_asc') {
this.sortByMetricInDirection('asc', '.latency99 .value');
} else if(this.sortedBy == 'lat99_desc') {
this.sortByMetricInDirection('desc', '.latency99 .value');
} else if(this.sortedBy == 'lat995_asc') {
this.sortByMetricInDirection('asc', '.latency995 .value');
} else if(this.sortedBy == 'lat995_desc') {
this.sortByMetricInDirection('desc', '.latency995 .value');
} else if(this.sortedBy == 'latMean_asc') {
this.sortByMetricInDirection('asc', '.latencyMean .value');
} else if(this.sortedBy == 'latMean_desc') {
this.sortByMetricInDirection('desc', '.latencyMean .value');
} else if(this.sortedBy == 'latMedian_asc') {
this.sortByMetricInDirection('asc', '.latencyMedian .value');
} else if(this.sortedBy == 'latMedian_desc') {
this.sortByMetricInDirection('desc', '.latencyMedian .value');
}
};
// default sort type and direction
this.sortedBy = 'alph_asc';
// a temporary home for the logger until we become more sophisticated
function log(message) {
console.log(message);
};
function addCommas(nStr){
nStr += '';
if(nStr.length <=3) {
return nStr; //shortcut if we don't need commas
}
x = nStr.split('.');
x1 = x[0];
x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
}
})(window);

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,73 @@
<div class="counters">
<div class="cell line">
<a href="javascript://" title="Error Percentage [Timed-out + Threadpool Rejected + Failure / Total]" class="tooltip errorPercentage"><span class="value"><%= errorPercentage %></span> %</a>
</div>
<div class="cell borderRight">
<% if(propertyValue_executionIsolationStrategy == 'THREAD') { %>
<a href="javascript://" title="Timed-out Request Count" class="line tooltip timeout"><%= addCommas(rollingCountTimeout) %></a>
<a href="javascript://" title="Threadpool Rejected Request Count" class="line tooltip rejected"><%= addCommas(rollingCountThreadPoolRejected) %></a>
<% } %>
<a href="javascript://" title="Failure Request Count" class="line tooltip failure"><%= addCommas(rollingCountFailure) %></a>
</div>
<div class="cell borderRight">
<a href="javascript://" title="Successful Request Count" class="line tooltip success"><%= addCommas(rollingCountSuccess) %></a>
<a href="javascript://" title="Short-circuited Request Count" class="line tooltip shortCircuited"><%= addCommas(rollingCountShortCircuited) %></a>
<br>
</div>
</div>
<div class="rate">
<a href="javascript://" title="Total Request Rate per Second per Reporting Host" class="tooltip rate"><span class="smaller">Host: </span><span class="ratePerSecondPerHost"><%= addCommas(roundNumber(ratePerSecondPerHost)) %></span>/s</a>
</div>
<div class="rate">
<a href="javascript://" title="Total Request Rate per Second for Cluster" class="tooltip rate"><span class="smaller">Cluster: </span><span class="ratePerSecond"><%= addCommas(roundNumber(ratePerSecond)) %></span>/s</a>
</div>
<div class="circuitStatus">
<% if(propertyValue_circuitBreakerForceClosed) { %>
<span class="smaller">[ <font color="orange">Forced Closed</font> ]</span>
<% } %>
<% if(propertyValue_circuitBreakerForceOpen) { %>
Circuit <font color="red">Forced Open</font>
<% } else { %>
<% if(isCircuitBreakerOpen == reportingHosts) { %>
Circuit <font color="red">Open</font>
<% } else if(isCircuitBreakerOpen == 0) { %>
Circuit <font color="green">Closed</font>
<% } else {
/* We have some circuits that are open */
%>
Circuit <font color="orange"><%= isCircuitBreakerOpen.replace("true", "Open").replace("false", "Closed") %>)</font>
<% } %>
<% } %>
</div>
<div class="spacer"></div>
<div class="tableRow">
<% if(typeof reportingHosts != 'undefined') { %>
<div class="cell header">Hosts</div>
<div class="cell data"><%= reportingHosts %></div>
<% } else { %>
<div class="cell header">Host</div>
<div class="cell data">Single</div>
<% } %>
<div class="cell header">90th</div>
<div class="cell data latency90"><span class="value"><%= getInstanceAverage(latencyExecute['90'], reportingHosts, false) %></span>ms</div>
</div>
<div class="tableRow">
<div class="cell header">Median</div>
<div class="cell data latencyMedian"><span class="value"><%= getInstanceAverage(latencyExecute['50'], reportingHosts, false) %></span>ms</div>
<div class="cell header">99th</div>
<div class="cell data latency99"><span class="value"><%= getInstanceAverage(latencyExecute['99'], reportingHosts, false) %></span>ms</div>
</div>
<div class="tableRow">
<div class="cell header">Mean</div>
<div class="cell data latencyMean"><span class="value"><%= latencyExecute_mean %></span>ms</div>
<div class="cell header">99.5th</div>
<div class="cell data latency995"><span class="value"><%= getInstanceAverage(latencyExecute['99.5'], reportingHosts, false) %></span>ms</div>
</div>
<!-- <a href="">History</a> <a href="">EPIC</a> <a href="">Actions</a> -->

View File

@@ -0,0 +1,40 @@
<div class="monitor" id="CIRCUIT_<%= name %>" style="position:relative;">
<%
var displayName = name;
var toolTip = "";
if(displayName.length > 32) {
displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length);
toolTip = "title=\"" + name + "\"";
}
%>
<div id="chart_CIRCUIT_<%= name %>" class="chart" style="position:absolute;top:0px;left:0; float:left; width:100%; height:100%;"></div>
<div style="position:absolute;top:0x;width:100%;height:15px;opacity:0.8; background:white;">
<% if(includeDetailIcon) { %>
<p class="name" <%= toolTip %> style="padding-right:16px">
<%= displayName %>
<a href="../dependencies/command.jsp?name=<%= name %>"><img src="../components/hystrixCommand/magnifying-glass-icon-20.png" height="14px" width="14px" border="0" style="position: absolute; right:0px;"></a>
</p>
<% } else { %>
<p class="name" <%= toolTip %>><%= displayName %></p>
<% } %>
</div>
<div style="position:absolute;top:15px;; opacity:0.8; background:white; width:100%; height:95%;">
<div class="monitor_data"></div>
</div>
<div id="graph_CIRCUIT_<%= name %>" class="graph" style="position:absolute;top:25px;left:0; float:left; width:140px; height:62px;"></div>
<script>
var y = 200;
/* escape with two backslashes */
var vis = d3.select("#chart_CIRCUIT_<%= name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\\\$1') %>").append("svg:svg").attr("width", "100%").attr("height", "100%");
/* add a circle -- we don't use the data point, we set it manually, so just passing in [1] */
var circle = vis.selectAll("circle").data([1]).enter().append("svg:circle");
/* setup the initial styling and sizing of the circle */
circle.style("fill", "green").attr("cx", "30%").attr("cy", "30%").attr("r", 5);
/* add the line graph - it will be populated by javascript, no default to show here */
/* escape with two backslashes */
var graph = d3.select("#graph_CIRCUIT_<%= name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\\\$1') %>").append("svg:svg").attr("width", "100%").attr("height", "100%");
</script>
</div>

View File

@@ -0,0 +1,6 @@
<div class="tableRow">
<div class="cell header">Median</div>
<div class="cell data"><span class="value"><%= sla_medianLastMinute %></span>ms</div>
<div class="cell header">99th</div>
<div class="cell data"><span class="value"><%= sla_percentile99LastMinute %></span>ms</div>
</div>

View File

@@ -0,0 +1,141 @@
.dependencyThreadPools .spacer {
width: 100%;
margin: 0 auto;
padding-top:4px;
clear:both;
}
.dependencyThreadPools .last {
margin-right: 0px;
}
.dependencyThreadPools span.loading {
display: block;
padding-top: 6%;
padding-bottom: 6%;
color: gray;
text-align: center;
}
.dependencyThreadPools span.loading.failed {
color: red;
}
.dependencyThreadPools div.monitor {
float: left;
margin-right:5px; /* these are tweaked to look good on desktop and iPad portrait, and fit things densely */
margin-top:5px;
}
.dependencyThreadPools div.monitor p.name {
font-weight:bold;
font-size: 10pt;
text-align: right;
padding-bottom: 5px;
}
.dependencyThreadPools div.monitor_data {
margin: 0 auto;
}
.dependencyThreadPools span.smaller {
font-size: 8pt;
color: grey;
}
.dependencyThreadPools div.tableRow {
width:100%;
white-space: nowrap;
font-size: 8pt;
margin: 0 auto;
clear:both;
}
.dependencyThreadPools div.tableRow .cell {
float:left;
}
.dependencyThreadPools div.tableRow .header {
text-align:right;
padding-right:5px;
}
.dependencyThreadPools div.tableRow .header.left {
width:85px;
}
.dependencyThreadPools div.tableRow .header.right {
width:75px;
}
.dependencyThreadPools div.tableRow .data {
font-weight: bold;
text-align:right;
}
.dependencyThreadPools div.tableRow .data.left {
width:30px;
}
.dependencyThreadPools div.tableRow .data.right {
width:45px;
}
.dependencyThreadPools div.monitor {
width: 245px; /* we want a fixed width instead of percentage as I want the boxes to be a set size and then fill in as many as can fit in each row ... this allows 3 columns on an iPad */
height: 110px;
}
/* override the HREF when we have specified it as a tooltip to not act like a link */
.dependencyThreadPools div.monitor_data a.tooltip {
text-decoration: none;
cursor: default;
}
.dependencyThreadPools div.monitor_data a.rate {
font-weight:bold;
color: black;
font-size: 11pt;
}
.dependencyThreadPools div.rate {
padding-top: 1px;
clear:both;
text-align:right;
}
.dependencyThreadPools span.rate_value {
font-weight:bold;
}
.dependencyThreadPools div.monitor div.chart {
}
.dependencyThreadPools div.monitor div.chart svg {
}
.dependencyThreadPools div.monitor div.chart svg text {
fill: white;
}
.dependencyThreadPools #hidden {
width:1px;
height:1px;
background: lightgrey;
display: none;
}

View File

@@ -0,0 +1,346 @@
(function(window) {
// cache the templates we use on this page as global variables (asynchronously)
jQuery.get(getRelativePath("../components/hystrixThreadPool/templates/hystrixThreadPool.html"), function(data) {
htmlTemplate = data;
});
jQuery.get(getRelativePath("../components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html"), function(data) {
htmlTemplateContainer = data;
});
function getRelativePath(path) {
var p = location.pathname.slice(0, location.pathname.lastIndexOf("/")+1);
return p + path;
}
/**
* Object containing functions for displaying and updating the UI with streaming data.
*
* Publish this externally as "HystrixThreadPoolMonitor"
*/
window.HystrixThreadPoolMonitor = function(containerId) {
var self = this; // keep scope under control
this.containerId = containerId;
/**
* Initialization on construction
*/
// intialize various variables we use for visualization
var maxXaxisForCircle="40%";
var maxYaxisForCircle="40%";
var maxRadiusForCircle="125";
var maxDomain = 2000;
self.circleRadius = d3.scale.pow().exponent(0.5).domain([0, maxDomain]).range(["5", maxRadiusForCircle]); // requests per second per host
self.circleYaxis = d3.scale.linear().domain([0, maxDomain]).range(["30%", maxXaxisForCircle]);
self.circleXaxis = d3.scale.linear().domain([0, maxDomain]).range(["30%", maxYaxisForCircle]);
self.colorRange = d3.scale.linear().domain([10, 25, 40, 50]).range(["green", "#FFCC00", "#FF9900", "red"]);
self.errorPercentageColorRange = d3.scale.linear().domain([0, 10, 35, 50]).range(["grey", "black", "#FF9900", "red"]);
/**
* We want to keep sorting in the background since data values are always changing, so this will re-sort every X milliseconds
* to maintain whatever sort the user (or default) has chosen.
*
* In other words, sorting only for adds/deletes is not sufficient as all but alphabetical sort are dynamically changing.
*/
setInterval(function() {
// sort since we have added a new one
self.sortSameAsLast();
}, 1000)
/**
* END of Initialization on construction
*/
/**
* Event listener to handle new messages from EventSource as streamed from the server.
*/
/* public */ self.eventSourceMessageListener = function(e) {
var data = JSON.parse(e.data);
if(data) {
// check for reportingHosts (if not there, set it to 1 for singleHost vs cluster)
if(!data.reportingHosts) {
data.reportingHosts = 1;
}
if(data && data.type == 'HystrixThreadPool') {
if (data.deleteData == 'true') {
deleteThreadPool(data.escapedName);
} else {
displayThreadPool(data);
}
}
}
}
/**
* Pre process the data before displying in the UI.
* e.g Get Averages from sums, do rate calculation etc.
*/
function preProcessData(data) {
validateData(data);
// escape string used in jQuery & d3 selectors
data.escapedName = data.name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\$1');
// do math
converAllAvg(data);
calcRatePerSecond(data);
}
function converAllAvg(data) {
convertAvg(data, "propertyValue_queueSizeRejectionThreshold", false);
// the following will break when it becomes a compound string if the property is dynamically changed
convertAvg(data, "propertyValue_metricsRollingStatisticalWindowInMilliseconds", false);
}
function convertAvg(data, key, decimal) {
if (decimal) {
data[key] = roundNumber(data[key]/data["reportingHosts"]);
} else {
data[key] = Math.floor(data[key]/data["reportingHosts"]);
}
}
function calcRatePerSecond(data) {
var numberSeconds = data["propertyValue_metricsRollingStatisticalWindowInMilliseconds"] / 1000;
var totalThreadsExecuted = data["rollingCountThreadsExecuted"];
if (totalThreadsExecuted < 0) {
totalThreadsExecuted = 0;
}
data["ratePerSecond"] = roundNumber(totalThreadsExecuted / numberSeconds);
data["ratePerSecondPerHost"] = roundNumber(totalThreadsExecuted / numberSeconds / data["reportingHosts"]);
}
function validateData(data) {
assertNotNull(data,"type");
assertNotNull(data,"name");
// assertNotNull(data,"currentTime");
assertNotNull(data,"currentActiveCount");
assertNotNull(data,"currentCompletedTaskCount");
assertNotNull(data,"currentCorePoolSize");
assertNotNull(data,"currentLargestPoolSize");
assertNotNull(data,"currentMaximumPoolSize");
assertNotNull(data,"currentPoolSize");
assertNotNull(data,"currentQueueSize");
assertNotNull(data,"currentTaskCount");
assertNotNull(data,"rollingCountThreadsExecuted");
assertNotNull(data,"rollingMaxActiveThreads");
assertNotNull(data,"reportingHosts");
assertNotNull(data,"propertyValue_queueSizeRejectionThreshold");
assertNotNull(data,"propertyValue_metricsRollingStatisticalWindowInMilliseconds");
}
function assertNotNull(data, key) {
if(data[key] == undefined) {
if (key == "dependencyOwner") {
data["dependencyOwner"] = data.name;
} else {
throw new Error("Key Missing: " + key + " for " + data.name)
}
}
}
/**
* Method to display the THREAD_POOL data
*
* @param data
*/
/* private */ function displayThreadPool(data) {
try {
preProcessData(data);
} catch (err) {
log("Failed preProcessData: " + err.message);
return;
}
// add the 'addCommas' function to the 'data' object so the HTML templates can use it
data.addCommas = addCommas;
// add the 'roundNumber' function to the 'data' object so the HTML templates can use it
data.roundNumber = roundNumber;
var addNew = false;
// check if we need to create the container
if(!$('#THREAD_POOL_' + data.escapedName).length) {
// it doesn't exist so add it
var html = tmpl(htmlTemplateContainer, data);
// remove the loading thing first
$('#' + containerId + ' span.loading').remove();
// get the current last column and remove the 'last' class from it
$('#' + containerId + ' div.last').removeClass('last');
// now create the new data and add it
$('#' + containerId + '').append(html);
// add the 'last' class to the column we just added
$('#' + containerId + ' div.monitor').last().addClass('last');
// add the default sparkline graph
d3.selectAll('#graph_THREAD_POOL_' + data.escapedName + ' svg').append("svg:path");
// remember this is new so we can trigger a sort after setting data
addNew = true;
}
// set the rate on the div element so it's available for sorting
$('#THREAD_POOL_' + data.escapedName).attr('rate_value', data.ratePerSecondPerHost);
// now update/insert the data
$('#THREAD_POOL_' + data.escapedName + ' div.monitor_data').html(tmpl(htmlTemplate, data));
// set variables for circle visualization
var rate = data.ratePerSecondPerHost;
// we will treat each item in queue as 1% of an error visualization
// ie. 5 threads in queue per instance == 5% error percentage
var errorPercentage = data.currentQueueSize / data.reportingHosts;
updateCircle('#THREAD_POOL_' + data.escapedName + ' circle', rate, errorPercentage);
if(addNew) {
// sort since we added a new circuit
self.sortSameAsLast();
}
}
/* round a number to X digits: num => the number to round, dec => the number of decimals */
/* private */ function roundNumber(num) {
var dec=1; // we are hardcoding to support only 1 decimal so that our padding logic at the end is simple
var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
var resultAsString = result.toString();
if(resultAsString.indexOf('.') == -1) {
resultAsString = resultAsString + '.';
for(var i=0; i<dec; i++) {
resultAsString = resultAsString + '0';
}
}
return resultAsString;
};
/* private */ function updateCircle(cssTarget, rate, errorPercentage) {
var newXaxisForCircle = self.circleXaxis(rate);
if(parseInt(newXaxisForCircle) > parseInt(maxXaxisForCircle)) {
newXaxisForCircle = maxXaxisForCircle;
}
var newYaxisForCircle = self.circleYaxis(rate);
if(parseInt(newYaxisForCircle) > parseInt(maxYaxisForCircle)) {
newYaxisForCircle = maxYaxisForCircle;
}
var newRadiusForCircle = self.circleRadius(rate);
if(parseInt(newRadiusForCircle) > parseInt(maxRadiusForCircle)) {
newRadiusForCircle = maxRadiusForCircle;
}
d3.selectAll(cssTarget)
.transition()
.duration(400)
.attr("cy", newYaxisForCircle)
.attr("cx", newXaxisForCircle)
.attr("r", newRadiusForCircle)
.style("fill", self.colorRange(errorPercentage));
}
/* private */ function deleteThreadPool(poolName) {
$('#THREAD_POOL_' + poolName).remove();
}
}
// public methods for sorting
HystrixThreadPoolMonitor.prototype.sortByVolume = function() {
var direction = "desc";
if(this.sortedBy == 'rate_desc') {
direction = 'asc';
}
this.sortByVolumeInDirection(direction);
}
HystrixThreadPoolMonitor.prototype.sortByVolumeInDirection = function(direction) {
this.sortedBy = 'rate_' + direction;
$('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'rate_value'});
}
HystrixThreadPoolMonitor.prototype.sortAlphabetically = function() {
var direction = "asc";
if(this.sortedBy == 'alph_asc') {
direction = 'desc';
}
this.sortAlphabeticalInDirection(direction);
}
HystrixThreadPoolMonitor.prototype.sortAlphabeticalInDirection = function(direction) {
this.sortedBy = 'alph_' + direction;
$('#' + this.containerId + ' div.monitor').tsort("p.name", {order: direction});
}
HystrixThreadPoolMonitor.prototype.sortByMetricInDirection = function(direction, metric) {
$('#' + this.containerId + ' div.monitor').tsort(metric, {order: direction});
}
// this method is for when new divs are added to cause the elements to be sorted to whatever the user last chose
HystrixThreadPoolMonitor.prototype.sortSameAsLast = function() {
if(this.sortedBy == 'alph_asc') {
this.sortAlphabeticalInDirection('asc');
} else if(this.sortedBy == 'alph_desc') {
this.sortAlphabeticalInDirection('desc');
} else if(this.sortedBy == 'rate_asc') {
this.sortByVolumeInDirection('asc');
} else if(this.sortedBy == 'rate_desc') {
this.sortByVolumeInDirection('desc');
} else if(this.sortedBy == 'error_asc') {
this.sortByErrorInDirection('asc');
} else if(this.sortedBy == 'error_desc') {
this.sortByErrorInDirection('desc');
} else if(this.sortedBy == 'lat90_asc') {
this.sortByMetricInDirection('asc', 'p90');
} else if(this.sortedBy == 'lat90_desc') {
this.sortByMetricInDirection('desc', 'p90');
} else if(this.sortedBy == 'lat99_asc') {
this.sortByMetricInDirection('asc', 'p99');
} else if(this.sortedBy == 'lat99_desc') {
this.sortByMetricInDirection('desc', 'p99');
} else if(this.sortedBy == 'lat995_asc') {
this.sortByMetricInDirection('asc', 'p995');
} else if(this.sortedBy == 'lat995_desc') {
this.sortByMetricInDirection('desc', 'p995');
} else if(this.sortedBy == 'latMean_asc') {
this.sortByMetricInDirection('asc', 'pMean');
} else if(this.sortedBy == 'latMean_desc') {
this.sortByMetricInDirection('desc', 'pMean');
} else if(this.sortedBy == 'latMedian_asc') {
this.sortByMetricInDirection('asc', 'pMedian');
} else if(this.sortedBy == 'latMedian_desc') {
this.sortByMetricInDirection('desc', 'pMedian');
}
}
// default sort type and direction
this.sortedBy = 'alph_asc';
// a temporary home for the logger until we become more sophisticated
function log(message) {
console.log(message);
};
function addCommas(nStr){
nStr += '';
if(nStr.length <=3) {
return nStr; //shortcut if we don't need commas
}
x = nStr.split('.');
x1 = x[0];
x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
}
})(window)

View File

@@ -0,0 +1,33 @@
<div class="spacer"></div>
<div class="rate">
<a href="javascript://" title="Total Execution Rate per Second per Reporting Host" class="tooltip rate"><span class="smaller">Host: </span><span class="ratePerSecondPerHost"><%= addCommas(ratePerSecondPerHost) %></span>/s</a>
</div>
<div class="rate">
<a href="javascript://" title="Total Execution Rate per Second for Cluster" class="tooltip rate"><span class="smaller">Cluster: </span><span class="ratePerSecond"><%= addCommas(ratePerSecond) %></span>/s</a>
</div>
<div class="spacer"></div>
<div class="tableRow">
<div class="cell header left">Active</div>
<div class="cell data left"><%= currentActiveCount%></div>
<div class="cell header right">Max Active</div>
<div class="cell data right"><%= addCommas(rollingMaxActiveThreads)%></div>
</div>
<div class="tableRow">
<div class="cell header left">Queued</div>
<div class="cell data left"><%= currentQueueSize %></div>
<div class="cell header right">Executions</div>
<div class="cell data right"><%= addCommas(rollingCountThreadsExecuted)%></div>
</div>
<div class="tableRow">
<div class="cell header left">Pool Size</div>
<div class="cell data left"><%= currentPoolSize %></div>
<div class="cell header right">Queue Size</div>
<div class="cell data right"><%= propertyValue_queueSizeRejectionThreshold %></div>
</div>

View File

@@ -0,0 +1,34 @@
<div class="monitor" id="THREAD_POOL_<%= name %>" style="position:relative;">
<%
var displayName = name;
var toolTip = "";
if(displayName.length > 32) {
displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length);
toolTip = "title=\"" + name + "\"";
}
%>
<div id="chart_THREAD_POOL_<%= name %>" class="chart" style="position:absolute;top:0px;left:0; float:left; width:100%; height:100%;"></div>
<div style="position:absolute;top:0x;width:100%;height:15px;opacity:0.8; background:white;"><p class="name" <%= toolTip %>><%= displayName %></p></div>
<div style="position:absolute;top:15px;; opacity:0.8; background:white; width:100%; height:95%;">
<div class="monitor_data"></div>
</div>
<script>
<% if(typeof errorPercentage != 'undefined') { %>
var errorPercentageData = [<%= errorPercentage %>];
<% } else { %>
var errorPercentageData = [99];
<% } %>
var y = 200;
/* escape with two backslashes */
var vis = d3.select("#chart_THREAD_POOL_<%= name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\\\$1') %>").append("svg:svg").attr("width", "100%").attr("height", "100%");
/* add a circle -- we don't use the data point, we set it manually, so just passing in [1] */
var circle = vis.selectAll("circle").data([1]).enter().append("svg:circle");
/* setup the initial styling and sizing of the circle */
circle.style("fill", "green").attr("cx", "30%").attr("cy", "20%").attr("r", 5);
</script>
</div>

View File

@@ -0,0 +1,71 @@
@IMPORT url("resets.css");
body {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
img, object, embed {
max-width: 100%;
}
img {
height: auto;
}
#header {
background: #FFFFFF url(http://raw.github.com/wiki/Netflix/Hystrix/images/hystrix-logo-tagline-tiny.png) no-repeat scroll 99% 0%;
height: 65px;
margin-bottom: 5px;
}
#header h2 {
float:left;
color: black;
position:relative;
padding-left: 20px;
top: 26px;
font-size: 20px;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
#header .header_nav {
position:absolute;
top:48px;
right:15px;
}
#header .header_links {
float:left;
color: lightgray;
font-size: 18px;
top: 3px;
padding-left: 10px;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
#header .header_links a {
color: white;
}
#header .header_clusters {
float:left;
position:relative;
padding-left: 10px;
top: -1px;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
@media screen and (min-width: 1500px) {
#header .header_nav {
top:13px;
right:130px;
}
#header {
background: #FFFFFF url(http://raw.github.com/wiki/Netflix/Hystrix/images/hystrix-logo-tagline-tiny.png) no-repeat scroll 99% 50%;
height: 65px;
}
}

View File

@@ -0,0 +1,102 @@
/*
html5doctor.com Reset Stylesheet
v1.6.1
Last Updated: 2010-09-17
Author: Richard Clark - http://richclarkdesign.com
Twitter: @rich_clark
*/
html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var,
b, i,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
margin:0;
padding:0;
border:0;
outline:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
}
body {
line-height:1;
}
article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
display:block;
}
nav ul {
list-style:none;
}
blockquote, q {
quotes:none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content:'';
content:none;
}
a {
margin:0;
padding:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
}
/* change colours to suit your needs */
ins {
background-color:#ff9;
color:#000;
text-decoration:none;
}
/* change colours to suit your needs */
mark {
background-color:#ff9;
color:#000;
font-style:italic;
font-weight:bold;
}
del {
text-decoration: line-through;
}
abbr[title], dfn[title] {
border-bottom:1px dotted;
cursor:help;
}
table {
border-collapse:collapse;
border-spacing:0;
}
/* change border colour to suit your needs */
hr {
display:block;
height:1px;
border:0;
border-top:1px solid #cccccc;
margin:1em 0;
padding:0;
}
input, select {
vertical-align:middle;
}

View File

@@ -0,0 +1,21 @@
/* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
* http://simplegrid.info
* by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
* License: http://creativecommons.org/licenses/MIT/ */
/* Containers */
body { font-size: 1.125em; }
.grid{ width:1206px; }
/* 6-Col Grid Sizes */
.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:176px; } /* Sixths */
.slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:382px; } /* Thirds */
.slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:794px; } /* Two-Thirds */
.slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:1000px; } /* Five-Sixths */
/* 4-Col Grid Sizes */
.slot-6,.slot-7,.slot-8,.slot-9{ width:279px; } /* Quarters */
.slot-6-7-8,.slot-7-8-9{ width:897px; } /* Three-Quarters */
/* 6-Col/4-Col Shared Grid Sizes */
.slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:588px; } /* Halves */

View File

@@ -0,0 +1,33 @@
/* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
* http://simplegrid.info
* by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
* License: http://creativecommons.org/licenses/MIT/ */
/* Containers */
body { font-size: 0.875em; padding: 0; }
.grid{ margin:0 auto; padding: 0 10px; width:700px; }
.row{ clear:left; }
/* Slots Setup */
.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:20px; }
/* 6-Col Grid Sizes */
.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:100px; } /* Sixths */
.slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:220px; } /* Thirds */
.slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:460px; } /* Two-Thirds */
.slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:580px; } /* Five-Sixths */
/* 4-Col Grid Sizes */
.slot-6,.slot-7,.slot-8,.slot-9{ width:160px; } /* Quarters */
.slot-6-7-8,.slot-7-8-9{ width:520px; } /* Three-Quarters */
/* 6-Col/4-Col Shared Grid Sizes */
.slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:340px; } /* Halves */
.slot-0-1-2-3-4-5, .slot-6-7-8-9{ width: 100%; } /* Full-Width */
/* Zeroing Out Leftmost Slot Margins */
.slot-0,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-6,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-1 .slot-1,.slot-1-2 .slot-1,.slot-1-2 .slot-1-2,.slot-1-2-3 .slot-1,.slot-1-2-3 .slot-1-2,.slot-1-2-3 .slot-1-2-3,.slot-1-2-3-4 .slot-1,.slot-1-2-3-4 .slot-1-2,.slot-1-2-3-4 .slot-1-2-3,.slot-1-2-3-4 .slot-1-2-3-4,.slot-1-2-3-4-5 .slot-1,.slot-1-2-3-4-5 .slot-1-2,.slot-1-2-3-4-5 .slot-1-2-3,.slot-1-2-3-4-5 .slot-1-2-3-4,.slot-1-2-3-4-5 .slot-1-2-3-4-5,.slot-2 .slot-2,.slot-2-3 .slot-2,.slot-2-3 .slot-2-3,.slot-2-3-4 .slot-2,.slot-2-3-4 .slot-2-3,.slot-2-3-4 .slot-2-3-4,.slot-2-3-4-5 .slot-2,.slot-2-3-4-5 .slot-2-3,.slot-2-3-4-5 .slot-2-3-4,.slot-2-3-4-5 .slot-2-3-4-5,.slot-3 .slot-3,.slot-3-4 .slot-3,.slot-3-4 .slot-3-4,.slot-3-4-5 .slot-3,.slot-3-4-5 .slot-3-4,.slot-3-4-5 .slot-3-4-5,.slot-4 .slot-4,.slot-4-5 .slot-4,.slot-4-5 .slot-4-5,.slot-5 .slot-5,.slot-7 .slot-7,.slot-7-8 .slot-7,.slot-7-8 .slot-7-8,.slot-7-8-9 .slot-7,.slot-7-8-9 .slot-7-8,.slot-7-8-9 .slot-7-8-9,.slot-8 .slot-8,.slot-8-9 .slot-8,.slot-8-9 .slot-8-9{ margin-left:0 !important; } /* Important is to avoid repeating this in larger screen css files */
/* Row Clearfix */
.row:after{ visibility:hidden; display:block; font-size:0; content:" "; clear:both; height:0; }
.row{ zoom:1; }

View File

@@ -0,0 +1,24 @@
/* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
* http://simplegrid.info
* by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
* License: http://creativecommons.org/licenses/MIT/ */
/* Containers */
body { font-size: 100%; }
.grid{ width:966px; }
/* Slots Setup */
.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:30px; }
/* 6-Col Grid Sizes */
.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:136px; } /* Sixths */
.slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:302px; } /* Thirds */
.slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:634px; } /* Two-Thirds */
.slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:800px; } /* Five-Sixths */
/* 4-Col Grid Sizes */
.slot-6,.slot-7,.slot-8,.slot-9{ width:219px; } /* Quarters */
.slot-6-7-8,.slot-7-8-9{ width:717px; } /* Three-Quarters */
/* 6-Col/4-Col Shared Grid Sizes */
.slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:468px; } /* Halves */

View File

@@ -0,0 +1,19 @@
Copyright (c) 2011 Crowd Favorite, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1 @@
http://simplegrid.info/

View File

@@ -0,0 +1,27 @@
/* Extension of SimpleGrid by benjchristensen to allow percentage based sizing on very large displays
*
* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
* http://simplegrid.info
* by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
* License: http://creativecommons.org/licenses/MIT/ */
/* Containers */
body { font-size: 1.125em; }
.grid{ width:100%; }
/* Slots Setup */
.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:0px; }
/* 6-Col Grid Sizes */
.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:16.6%; } /* Sixths */
.slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:33.3%; } /* Thirds */
.slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:66.6%; } /* Two-Thirds */
.slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:83.3%; } /* Five-Sixths */
/* 4-Col Grid Sizes */
.slot-6,.slot-7,.slot-8,.slot-9{ width:25%; } /* Quarters */
.slot-6-7-8,.slot-7-8-9{ width:75%; } /* Three-Quarters */
/* 6-Col/4-Col Shared Grid Sizes */
.slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:50%; } /* Halves */

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Hystrix Dashboard</title>
<!-- Javascript to monitor and display -->
<script src="/webjars/jquery/2.1.1/jquery.min.js" type="text/javascript"></script>
<script>
function sendToMonitor() {
if($('#stream').val().length > 0) {
var url = "/hystrix/monitor/monitor.html?stream=" + encodeURIComponent($('#stream').val()) + "";
if($('#delay').val().length > 0) {
url += "&delay=" + $('#delay').val();
}
if($('#title').val().length > 0) {
url += "&title=" + encodeURIComponent($('#title').val());
}
location.href= url;
} else {
$('#message').html("The 'stream' value is required.");
}
}
</script>
</head>
<body>
<div style="width:800px;margin:0 auto;">
<center>
<img width="264" height="233" src="https://raw.github.com/wiki/Netflix/Hystrix/images/hystrix-logo.png">
<br>
<br>
<h2>Hystrix Dashboard</h2>
<input id="stream" type="textfield" size="120" placeholder="http://hostname:port/turbine/turbine.stream"></input>
<br><br>
<i>Cluster via Turbine (default cluster):</i> http://turbine-hostname:port/turbine.stream
<br>
<i>Cluster via Turbine (custom cluster):</i> http://turbine-hostname:port/turbine.stream?cluster=[clusterName]
<br>
<i>Single Hystrix App:</i> http://hystrix-app:port/hystrix.stream
<br><br>
Delay: <input id="delay" type="textfield" size="10" placeholder="2000"></input>ms
&nbsp;&nbsp;&nbsp;&nbsp;
Title: <input id="title" type="textfield" size="60" placeholder="Example Hystrix App"></input><br>
<br>
<button onclick="sendToMonitor()">Monitor Stream</button>
<br><br>
<div id="message" style="color:red"></div>
</center>
</div>
</body>
</html>

View File

@@ -0,0 +1,12 @@
/*
* jQuery TinySort - A plugin to sort child nodes by (sub) contents or attributes.
*
* Version: 1.0.5
*
* Copyright (c) 2008-2011 Ron Valstar http://www.sjeiti.com/
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
(function(b){b.tinysort={id:"TinySort",version:"1.0.5",copyright:"Copyright (c) 2008-2011 Ron Valstar",uri:"http://tinysort.sjeiti.com/",defaults:{order:"asc",attr:"",place:"start",returns:false,useVal:false}};b.fn.extend({tinysort:function(h,j){if(h&&typeof(h)!="string"){j=h;h=null}var e=b.extend({},b.tinysort.defaults,j);var p={};this.each(function(t){var v=(!h||h=="")?b(this):b(this).find(h);var u=e.order=="rand"?""+Math.random():(e.attr==""?(e.useVal?v.val():v.text()):v.attr(e.attr));var s=b(this).parent();if(!p[s]){p[s]={s:[],n:[]}}if(v.length>0){p[s].s.push({s:u,e:b(this),n:t})}else{p[s].n.push({e:b(this),n:t})}});for(var g in p){var d=p[g];d.s.sort(function k(t,s){var i=t.s.toLowerCase?t.s.toLowerCase():t.s;var u=s.s.toLowerCase?s.s.toLowerCase():s.s;if(c(t.s)&&c(s.s)){i=parseFloat(t.s);u=parseFloat(s.s)}return(e.order=="asc"?1:-1)*(i<u?-1:(i>u?1:0))})}var m=[];for(var g in p){var d=p[g];var n=[];var f=b(this).length;switch(e.place){case"first":b.each(d.s,function(s,t){f=Math.min(f,t.n)});break;case"org":b.each(d.s,function(s,t){n.push(t.n)});break;case"end":f=d.n.length;break;default:f=0}var q=[0,0];for(var l=0;l<b(this).length;l++){var o=l>=f&&l<f+d.s.length;if(a(n,l)){o=true}var r=(o?d.s:d.n)[q[o?0:1]].e;r.parent().append(r);if(o||!e.returns){m.push(r.get(0))}q[o?0:1]++}}return this.pushStack(m)}});function c(e){var d=/^\s*?[\+-]?(\d*\.?\d*?)\s*?$/.exec(e);return d&&d.length>0?d[1]:false}function a(e,f){var d=false;b.each(e,function(h,g){if(!d){d=g==f}});return d}b.fn.TinySort=b.fn.Tinysort=b.fn.tsort=b.fn.tinysort})(jQuery);

View File

@@ -0,0 +1,43 @@
//Simple JavaScript Templating
//John Resig - http://ejohn.org/ - MIT Licensed
// http://ejohn.org/blog/javascript-micro-templating/
(function(window, undefined) {
var cache = {};
window.tmpl = function tmpl(str, data) {
try {
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
//console.log(fn);
// Provide some basic currying to the user
return data ? fn(data) : fn;
}catch(e) {
console.log(e);
}
};
})(window);

View File

@@ -0,0 +1,101 @@
.container {
padding-left: 20px;
padding-right: 20px;
}
.row {
width: 100%;
margin: 0 auto;
overflow: hidden;
}
.spacer {
width: 100%;
margin: 0 auto;
padding-top:4px;
clear:both;
}
.last {
margin-right: 0px;
}
.menubar {
overflow: hidden;
border-bottom: 1px solid black;
}
.menubar div {
padding-bottom:5px;
margin: 0 auto;
overflow: hidden;
font-size: 80%;
font-family:'Bookman Old Style',Bookman,'URW Bookman L','Palatino Linotype',serif;
float:left;
}
.menubar .title {
float: left;
padding-right: 20px;
font-size: 110%;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: bold;
vertical-align: bottom;
}
.menubar .menu_actions {
float: left;
position:relative;
top: 4px;
}
.menubar .menu_legend {
float: right;
position:relative;
top: 4px;
}
h3.sectionHeader {
color: black;
font-size: 110%;
padding-top: 4px;
padding-bottom: 4px;
padding-left: 8px;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
background: lightgrey;
}
.success {
color: green;
}
.shortCircuited {
color: blue;
}
.timeout {
color: #FF9900; /* shade of orange */
}
.failure {
color: red;
}
.rejected {
color: purple;
}
.exceptionsThrown {
color: brown;
}
@media screen and (max-width: 1100px) {
.container {
padding-left: 5px;
padding-right: 5px;
}
}

View File

@@ -0,0 +1,197 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Hystrix Monitor</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Setup base for everything -->
<link rel="stylesheet" type="text/css" href="../css/global.css" />
<!-- Our custom CSS -->
<link rel="stylesheet" type="text/css" href="monitor.css" />
<!-- d3 -->
<script type="text/javascript" src="/webjars/d3js/3.4.11/d3.min.js" ></script>
<!-- Javascript to monitor and display -->
<script type="text/javascript" src="/webjars/jquery/2.1.1/jquery.min.js" ></script>
<script type="text/javascript" src="../js/jquery.tinysort.min.js"></script>
<script type="text/javascript" src="../js/tmpl.js"></script>
<!-- HystrixCommand -->
<script type="text/javascript" src="../components/hystrixCommand/hystrixCommand.js"></script>
<link rel="stylesheet" type="text/css" href="../components/hystrixCommand/hystrixCommand.css" />
<!-- HystrixThreadPool -->
<script type="text/javascript" src="../components/hystrixThreadPool/hystrixThreadPool.js"></script>
<link rel="stylesheet" type="text/css" href="../components/hystrixThreadPool/hystrixThreadPool.css" />
</head>
<body>
<div id="header">
<h2><span id="title_name"></span></h2>
</div>
<div class="container">
<div class="row">
<div class="menubar">
<div class="title">
Circuit
</div>
<div class="menu_actions">
Sort:
<a href="javascript://" onclick="hystrixMonitor.sortByErrorThenVolume();">Error then Volume</a> |
<a href="javascript://" onclick="hystrixMonitor.sortAlphabetically();">Alphabetical</a> |
<a href="javascript://" onclick="hystrixMonitor.sortByVolume();">Volume</a> |
<a href="javascript://" onclick="hystrixMonitor.sortByError();">Error</a> |
<a href="javascript://" onclick="hystrixMonitor.sortByLatencyMean();">Mean</a> |
<a href="javascript://" onclick="hystrixMonitor.sortByLatencyMedian();">Median</a> |
<a href="javascript://" onclick="hystrixMonitor.sortByLatency90();">90</a> |
<a href="javascript://" onclick="hystrixMonitor.sortByLatency99();">99</a> |
<a href="javascript://" onclick="hystrixMonitor.sortByLatency995();">99.5</a>
</div>
<div class="menu_legend">
<span class="success">Success</span> | <span class="shortCircuited">Short-Circuited</span> | <span class="timeout">Timeout</span> | <span class="rejected">Rejected</span> | <span class="failure">Failure</span> | <span class="errorPercentage">Error %</span>
</div>
</div>
</div>
<div id="dependencies" class="row dependencies"><span class="loading">Loading ...</span></div>
<div class="spacer"></div>
<div class="spacer"></div>
<div class="row">
<div class="menubar">
<div class="title">
Thread Pools
</div>
<div class="menu_actions">
Sort: <a href="javascript://" onclick="dependencyThreadPoolMonitor.sortAlphabetically();">Alphabetical</a> |
<a href="javascript://" onclick="dependencyThreadPoolMonitor.sortByVolume();">Volume</a> |
</div>
</div>
</div>
<div id="dependencyThreadPools" class="row dependencyThreadPools"><span class="loading">Loading ...</span></div>
</div>
<script>
/**
* Queue up the monitor to start once the page has finished loading.
*
* This is an inline script and expects to execute once on page load.
*/
// commands
var hystrixMonitor = new HystrixCommandMonitor('dependencies', {includeDetailIcon:false});
var stream = getUrlVars()["stream"];
console.log("Stream: " + stream)
if(stream != undefined) {
if(getUrlVars()["delay"] != undefined) {
stream = stream + "&delay=" + getUrlVars()["delay"];
}
var commandStream = "/proxy.stream?origin=" + stream;
var poolStream = "/proxy.stream?origin=" + stream;
if(getUrlVars()["title"] != undefined) {
$('#title_name').html("Hystrix Stream: " + decodeURIComponent(getUrlVars()["title"]))
} else {
$('#title_name').html("Hystrix Stream: " + decodeURIComponent(stream))
}
}
console.log("Command Stream: " + commandStream)
$(window).load(function() { // within load with a setTimeout to prevent the infinite spinner
setTimeout(function() {
if(commandStream == undefined) {
console.log("commandStream is undefined")
$("#dependencies .loading").html("The 'stream' argument was not provided.");
$("#dependencies .loading").addClass("failed");
} else {
// sort by error+volume by default
hystrixMonitor.sortByErrorThenVolume();
// start the EventSource which will open a streaming connection to the server
var source = new EventSource(commandStream);
// add the listener that will process incoming events
source.addEventListener('message', hystrixMonitor.eventSourceMessageListener, false);
// source.addEventListener('open', function(e) {
// console.console.log(">>> opened connection, phase: " + e.eventPhase);
// // Connection was opened.
// }, false);
source.addEventListener('error', function(e) {
if (e.eventPhase == EventSource.CLOSED) {
// Connection was closed.
console.log("Connection was closed on error: " + JSON.stringify(e));
} else {
console.log("Error occurred while streaming: " + JSON.stringify(e));
}
}, false);
}
},0);
});
// thread pool
var dependencyThreadPoolMonitor = new HystrixThreadPoolMonitor('dependencyThreadPools');
$(window).load(function() { // within load with a setTimeout to prevent the infinite spinner
setTimeout(function() {
if(poolStream == undefined) {
console.log("poolStream is undefined")
$("#dependencyThreadPools .loading").html("The 'stream' argument was not provided.");
$("#dependencyThreadPools .loading").addClass("failed");
} else {
dependencyThreadPoolMonitor.sortByVolume();
// start the EventSource which will open a streaming connection to the server
var source = new EventSource(poolStream);
// add the listener that will process incoming events
source.addEventListener('message', dependencyThreadPoolMonitor.eventSourceMessageListener, false);
// source.addEventListener('open', function(e) {
// console.console.log(">>> opened connection, phase: " + e.eventPhase);
// // Connection was opened.
// }, false);
source.addEventListener('error', function(e) {
if (e.eventPhase == EventSource.CLOSED) {
// Connection was closed.
console.log("Connection was closed on error: " + e);
} else {
console.log("Error occurred while streaming: " + e);
}
}, false);
}
},0);
});
//Read a page's GET URL variables and return them as an associative array.
// from: http://jquery-howto.blogspot.com/2009/09/get-url-parameters-values-with-jquery.html
function getUrlVars()
{
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}
</script>
</body>
</html>