From 34cbf7a64b14719a3809adea890673d30fe29e5e Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 2 Sep 2014 12:32:08 +0100 Subject: [PATCH] Make Hystrix dash embeddable --- .../annotations/EnableHystrixDashboard.java | 2 +- .../HystrixDashboardConfiguration.java | 2 +- .../hystrixCommand/hystrixCommand.css | 196 +++++++ .../hystrixCommand/hystrixCommand.js | 536 ++++++++++++++++++ .../magnifying-glass-icon-20.png | Bin 0 -> 888 bytes .../hystrixCommand/magnifying-glass-icon.png | Bin 0 -> 19105 bytes .../templates/hystrixCircuit.html | 73 +++ .../templates/hystrixCircuitContainer.html | 40 ++ .../templates/hystrixCircuitProperties.html | 6 + .../hystrixThreadPool/hystrixThreadPool.css | 141 +++++ .../hystrixThreadPool/hystrixThreadPool.js | 346 +++++++++++ .../templates/hystrixThreadPool.html | 33 ++ .../templates/hystrixThreadPoolContainer.html | 34 ++ .../resources/static/hystrix/css/global.css | 71 +++ .../resources/static/hystrix/css/resets.css | 102 ++++ .../hystrix/css/simplegrid/1236_grid.css | 21 + .../hystrix/css/simplegrid/720_grid.css | 33 ++ .../hystrix/css/simplegrid/986_grid.css | 24 + .../static/hystrix/css/simplegrid/LICENSE.txt | 19 + .../static/hystrix/css/simplegrid/README.txt | 1 + .../css/simplegrid/percentage_grid.css | 27 + .../main/resources/static/hystrix/index.html | 56 ++ .../static/hystrix/js/jquery.tinysort.min.js | 12 + .../main/resources/static/hystrix/js/tmpl.js | 43 ++ .../static/hystrix/monitor/monitor.css | 101 ++++ .../static/hystrix/monitor/monitor.html | 197 +++++++ 26 files changed, 2114 insertions(+), 2 deletions(-) rename spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/{ => dashboard}/HystrixDashboardConfiguration.java (99%) create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon-20.png create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon.png create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuit.html create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitContainer.html create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitProperties.html create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPool.html create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/css/global.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/css/resets.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/1236_grid.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/720_grid.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/986_grid.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/LICENSE.txt create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/README.txt create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/percentage_grid.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/index.html create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/js/jquery.tinysort.min.js create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/js/tmpl.js create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.css create mode 100644 spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.html diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/annotations/EnableHystrixDashboard.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/annotations/EnableHystrixDashboard.java index 3537346b..abf27bd7 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/annotations/EnableHystrixDashboard.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/annotations/EnableHystrixDashboard.java @@ -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; /** diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/HystrixDashboardConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java similarity index 99% rename from spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/HystrixDashboardConfiguration.java rename to spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java index 3ff80319..a584dc14 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/HystrixDashboardConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java @@ -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; diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.css new file mode 100644 index 00000000..71d5ee39 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.css @@ -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; +} + + +} diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js new file mode 100644 index 00000000..dc375759 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js @@ -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); + + diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon-20.png b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon-20.png new file mode 100644 index 0000000000000000000000000000000000000000..2833fe2c658d3634c50bb6c4da80ad268863bf71 GIT binary patch literal 888 zcmV-;1Bd*HP)TlwD|>WfaGMPv0bOKHB$9+GcjlDz4UTLbbYB6FWqW zLzpY|6Y*xuffx2d&k)AWy540vIBp3IPm=VpL3q`C=uaChNDt491h?7=);{6(=b}HE9vya*>huk zu_MDGVy_2OS2|QmdEa{Tz=5rI-ut5Hn;NsWj-AVpTb}09{KTmrz8~2Cb38t9T|lLj z_rv$!?Q4&8cg?0Z67cH;>NK*pF!NW6zvrj0=YGt6_wC`mCr-rs)-Cys*L(MOJoHT0 z*>p3lcW9VyP=L1tbq#)IHinoiX?i4VeH;u1Pg|BXSqWGJ(B9s@>7MQ3mu3sM;tOb` za&R#XRt6R`V5LBFfzf&w#w}s9BXsx6*L1ie($N`g-WC|mEA%`RO5oI>MnSOzb`e$z zV3-=YrY$r!Hbs?EZV_3n2sHg>i^FSR!8k8S7jK)!0p(}~3BrTJ;hZOu4pz|hNJp$v4^ zlw06(LTd2>%gLol;BwWW#9~KI{V_f=;w~)X)WFoi?}mW8e6DqY>4C+Imr0Bbm8NEA zKNXQuRiKEh4#$UIn>cwiSIQ=FIG|Q9tIiFz9#~pR^5<{IaLQ`7b8OriRI1{ZU9~Cu z_U(N>5_#aG*0%fWeGN@GT{^a%V`1(B7P(4o)1+P!ne%4(_$PA?sC+aTedzI zm7?~lQhVOJ5-K7U1?bUe^rjtcZGpAi@8BAFF!;3`tQYwyBy#2n8ScKV|5@<-d#@U> zn!3Py{B~l6UQ^^)NQ!5=<@i&Z``>}O{{<8gwu5(|RmJkXhREQ3BJo}F z>&34k*^p9u4#rYEBIlk?*jC0$=SQ#E!|PE5Jf9f!ybY1H+*Qx!M#MjjDeibEGCS-5 O00003P-Fd&d_x~*ls6cAY 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 literal 0 HcmV?d00001 diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuit.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuit.html new file mode 100644 index 00000000..e50a3524 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuit.html @@ -0,0 +1,73 @@ + + + + + +
+ <% if(propertyValue_circuitBreakerForceClosed) { %> + [ Forced Closed ] + <% } %> + <% if(propertyValue_circuitBreakerForceOpen) { %> + Circuit Forced Open + <% } else { %> + <% if(isCircuitBreakerOpen == reportingHosts) { %> + Circuit Open + <% } else if(isCircuitBreakerOpen == 0) { %> + Circuit Closed + <% } else { + /* We have some circuits that are open */ + %> + Circuit <%= isCircuitBreakerOpen.replace("true", "Open").replace("false", "Closed") %>) + <% } %> + <% } %> +
+ +
+ +
+ <% if(typeof reportingHosts != 'undefined') { %> +
Hosts
+
<%= reportingHosts %>
+ <% } else { %> +
Host
+
Single
+ <% } %> +
90th
+
<%= getInstanceAverage(latencyExecute['90'], reportingHosts, false) %>ms
+
+
+
Median
+
<%= getInstanceAverage(latencyExecute['50'], reportingHosts, false) %>ms
+
99th
+
<%= getInstanceAverage(latencyExecute['99'], reportingHosts, false) %>ms
+
+
+
Mean
+
<%= latencyExecute_mean %>ms
+
99.5th
+
<%= getInstanceAverage(latencyExecute['99.5'], reportingHosts, false) %>ms
+
+ + diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitContainer.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitContainer.html new file mode 100644 index 00000000..4d19dbe0 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitContainer.html @@ -0,0 +1,40 @@ +
+ <% + var displayName = name; + var toolTip = ""; + if(displayName.length > 32) { + displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length); + toolTip = "title=\"" + name + "\""; + } + %> + +
+
+ <% if(includeDetailIcon) { %> +

style="padding-right:16px"> + <%= displayName %> + +

+ <% } else { %> +

><%= displayName %>

+ <% } %> +
+
+
+
+
+ + +
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitProperties.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitProperties.html new file mode 100644 index 00000000..5b8c0fae --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitProperties.html @@ -0,0 +1,6 @@ +
+
Median
+
<%= sla_medianLastMinute %>ms
+
99th
+
<%= sla_percentile99LastMinute %>ms
+
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.css new file mode 100644 index 00000000..e82ea5cc --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.css @@ -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; +} + + diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js new file mode 100644 index 00000000..a5003bc4 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js @@ -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 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) + + diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPool.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPool.html new file mode 100644 index 00000000..1e653ccb --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPool.html @@ -0,0 +1,33 @@ + +
+ + + + +
+ +
+
Active
+
<%= currentActiveCount%>
+ +
Max Active
+
<%= addCommas(rollingMaxActiveThreads)%>
+
+ +
+
Queued
+
<%= currentQueueSize %>
+
Executions
+
<%= addCommas(rollingCountThreadsExecuted)%>
+
+
+
Pool Size
+
<%= currentPoolSize %>
+
Queue Size
+
<%= propertyValue_queueSizeRejectionThreshold %>
+
+ \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html new file mode 100644 index 00000000..6af0ef2c --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html @@ -0,0 +1,34 @@ +
+ + <% + var displayName = name; + var toolTip = ""; + if(displayName.length > 32) { + displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length); + toolTip = "title=\"" + name + "\""; + } + %> + +
+

><%= displayName %>

+
+
+
+ + + + +
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/global.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/global.css new file mode 100644 index 00000000..46898b11 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/global.css @@ -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; + } +} diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/resets.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/resets.css new file mode 100644 index 00000000..4d137c7e --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/resets.css @@ -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; +} \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/1236_grid.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/1236_grid.css new file mode 100644 index 00000000..052ee488 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/1236_grid.css @@ -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 */ \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/720_grid.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/720_grid.css new file mode 100644 index 00000000..0ef2432c --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/720_grid.css @@ -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; } \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/986_grid.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/986_grid.css new file mode 100644 index 00000000..83a59788 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/986_grid.css @@ -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 */ \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/LICENSE.txt b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/LICENSE.txt new file mode 100644 index 00000000..e942914a --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/LICENSE.txt @@ -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. \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/README.txt b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/README.txt new file mode 100644 index 00000000..15cfc8c3 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/README.txt @@ -0,0 +1 @@ +http://simplegrid.info/ \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/percentage_grid.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/percentage_grid.css new file mode 100644 index 00000000..13cb4b2d --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/percentage_grid.css @@ -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 */ \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/index.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/index.html new file mode 100644 index 00000000..1c0bc56b --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/index.html @@ -0,0 +1,56 @@ + + + + +Hystrix Dashboard + + + + + + + +
+ +
+ +
+
+ +

Hystrix Dashboard

+ +

+ Cluster via Turbine (default cluster): http://turbine-hostname:port/turbine.stream +
+ Cluster via Turbine (custom cluster): http://turbine-hostname:port/turbine.stream?cluster=[clusterName] +
+ Single Hystrix App: http://hystrix-app:port/hystrix.stream +

+ Delay: ms +      + Title:
+
+ +

+
+ +
+
+ + \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/jquery.tinysort.min.js b/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/jquery.tinysort.min.js new file mode 100644 index 00000000..40d62eb5 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/jquery.tinysort.min.js @@ -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)*(iu?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=f&&l0?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); \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/tmpl.js b/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/tmpl.js new file mode 100644 index 00000000..d7098f78 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/tmpl.js @@ -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); diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.css new file mode 100644 index 00000000..a38647f0 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.css @@ -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; + } +} diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.html new file mode 100644 index 00000000..2444f4ee --- /dev/null +++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.html @@ -0,0 +1,197 @@ + + + + + Hystrix Monitor + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
Loading ...
+ +
+
+ +
+ +
+
Loading ...
+
+ + + + + + + +