|
@@ -0,0 +1,542 @@
|
|
|
+
|
|
|
+(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) {
|
|
|
+ // set defaults for values that may be missing from older streams
|
|
|
+ setIfMissing(data, "rollingCountBadRequests", 0);
|
|
|
+ // assert all the values we need
|
|
|
+ validateData(data);
|
|
|
+ // escape string used in jQuery & d3 selectors
|
|
|
+ data.escapedName = data.name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\$1');
|
|
|
+ // do math
|
|
|
+ convertAllAvg(data);
|
|
|
+ calcRatePerSecond(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ function setIfMissing(data, key, defaultValue) {
|
|
|
+ if(data[key] == undefined) {
|
|
|
+ data[key] = defaultValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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 convertAllAvg(data) {
|
|
|
+ convertAvg(data, "errorPercentage", true);
|
|
|
+ convertAvg(data, "latencyExecute_mean", false);
|
|
|
+ convertAvg(data, "latencyTotal_mean", 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,"rollingCountBadRequests");
|
|
|
+ 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_circuitBreakerForceClosed");
|
|
|
+ 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);
|