hystrixCommand.js 19.5 KB
Newer Older
Dave Syer committed
1 2 3 4

(function(window) {

	// cache the templates we use on this page as global variables (asynchronously)
5
	jQuery.get(getRelativePath("components/hystrixCommand/templates/hystrixCircuit.html"), function(data) {
Dave Syer committed
6 7
		hystrixTemplateCircuit = data;
	});
8
	jQuery.get(getRelativePath("components/hystrixCommand/templates/hystrixCircuitContainer.html"), function(data) {
Dave Syer committed
9 10 11 12 13 14 15 16 17 18
		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.
19
	 *
Dave Syer committed
20 21 22
	 * Publish this externally as "HystrixCommandMonitor"
	 */
	window.HystrixCommandMonitor = function(containerId, args) {
23

Dave Syer committed
24 25 26 27 28
		var self = this; // keep scope under control
		self.args = args;
		if(self.args == undefined) {
			self.args = {};
		}
29

Dave Syer committed
30
		this.containerId = containerId;
31

Dave Syer committed
32 33 34 35 36 37 38
		/**
		 * Initialization on construction
		 */
		// intialize various variables we use for visualization
		var maxXaxisForCircle="40%";
		var maxYaxisForCircle="40%";
		var maxRadiusForCircle="125";
39

Dave Syer committed
40 41 42 43 44 45 46 47 48 49
		// 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.
50
		 *
Dave Syer committed
51 52 53 54 55 56 57
		 * 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);

58

Dave Syer committed
59 60 61
		/**
		 * END of Initialization on construction
		 */
62

Dave Syer committed
63 64 65 66 67 68 69 70 71 72
		/**
		 * 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;
				}
73

Dave Syer committed
74 75 76 77 78 79 80 81 82 83 84
				if(data && data.type == 'HystrixCommand') {
					if (data.deleteData == 'true') {
						deleteCircuit(data.escapedName);
					} else {
						displayCircuit(data);
					}
				}
			}
		};

		/**
85 86
		 * Pre process the data before displying in the UI.
		 * e.g   Get Averages from sums, do rate calculation etc.
Dave Syer committed
87 88
		 */
		function preProcessData(data) {
89 90 91
			// set defaults for values that may be missing from older streams
			setIfMissing(data, "rollingCountBadRequests", 0);
			// assert all the values we need
Dave Syer committed
92 93 94 95
			validateData(data);
			// escape string used in jQuery & d3 selectors
			data.escapedName = data.name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\$1');
			// do math
96
			convertAllAvg(data);
Dave Syer committed
97 98 99
			calcRatePerSecond(data);
		}

100 101 102 103 104 105
		function setIfMissing(data, key, defaultValue) {
			if(data[key] == undefined) {
				data[key] = defaultValue;
			}
		}

Dave Syer committed
106 107 108
		/**
		 * 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)
109 110
		 * so we must divide by it to get an average per instance value.
		 *
Dave Syer committed
111 112
		 * We want to do this on any numerical values where we want per instance rather than cluster-wide sum.
		 */
113
		function convertAllAvg(data) {
Dave Syer committed
114 115 116 117
			convertAvg(data, "errorPercentage", true);
			convertAvg(data, "latencyExecute_mean", false);
			convertAvg(data, "latencyTotal_mean", false);
		}
118

Dave Syer committed
119 120 121 122 123 124 125
		function convertAvg(data, key, decimal) {
			if (decimal) {
				data[key] = getInstanceAverage(data[key], data["reportingHosts"], decimal);
			} else {
				data[key] = getInstanceAverage(data[key], data["reportingHosts"], decimal);
			}
		}
126

Dave Syer committed
127 128 129 130 131 132 133
		function getInstanceAverage(value, reportingHosts, decimal) {
			if (decimal) {
				return roundNumber(value/reportingHosts);
			} else {
				return Math.floor(value/reportingHosts);
			}
		}
134

Dave Syer committed
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
		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");
168
            assertNotNull(data,"rollingCountBadRequests");
Dave Syer committed
169 170 171 172 173 174 175 176 177
            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");
Ryan Baxter committed
178
						assertNotNull(data,"propertyValue_circuitBreakerForceClosed");
Dave Syer committed
179 180 181 182 183 184 185 186 187 188
            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");
		}
189

Dave Syer committed
190 191 192 193 194 195 196 197
		function assertNotNull(data, key) {
			if(data[key] == undefined) {
				throw new Error("Key Missing: " + key + " for " + data.name);
			}
		}

		/**
		 * Method to display the CIRCUIT data
198
		 *
Dave Syer committed
199 200 201
		 * @param data
		 */
		/* private */ function displayCircuit(data) {
202

Dave Syer committed
203 204 205 206 207 208
			try {
				preProcessData(data);
			} catch (err) {
				log("Failed preProcessData: " + err.message);
				return;
			}
209

Dave Syer committed
210 211 212 213 214 215
			// 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;
216

Dave Syer committed
217 218 219 220 221 222 223 224 225
			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;
				}
226

Dave Syer committed
227 228 229 230 231 232
				// 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);
233

Dave Syer committed
234 235
				// add the default sparkline graph
				d3.selectAll('#graph_CIRCUIT_' + data.escapedName + ' svg').append("svg:path");
236

Dave Syer committed
237 238 239
				// remember this is new so we can trigger a sort after setting data
				addNew = true;
			}
240 241


Dave Syer committed
242 243
			// now update/insert the data
			$('#CIRCUIT_' + data.escapedName + ' div.monitor_data').html(tmpl(hystrixTemplateCircuit, data));
244

Dave Syer committed
245 246 247 248
			var ratePerSecond = data.ratePerSecond;
			var ratePerSecondPerHost = data.ratePerSecondPerHost;
			var ratePerSecondPerHostDisplay = ratePerSecondPerHost;
			var errorThenVolume = (data.errorPercentage * 100000000) +  ratePerSecond;
249

Dave Syer committed
250 251 252
			// 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);
253

Dave Syer committed
254 255
			// update errorPercentage color on page
			$('#CIRCUIT_' + data.escapedName + ' a.errorPercentage').css('color', self.circuitErrorPercentageColorRange(data.errorPercentage));
256

Dave Syer committed
257
			updateCircle('circuit', '#CIRCUIT_' + data.escapedName + ' circle', ratePerSecondPerHostDisplay, data.errorPercentage);
258

Dave Syer committed
259 260 261 262 263 264 265 266 267 268 269 270
			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();
			}
		}
271

Dave Syer committed
272 273 274 275 276 277 278 279 280 281
		/* 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;
		};
282 283 284 285




Dave Syer committed
286 287 288 289 290 291 292 293 294 295 296 297 298
		/* 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;
			}
299

Dave Syer committed
300 301 302 303 304 305 306 307
			d3.selectAll(cssTarget)
				.transition()
				.duration(400)
				.attr("cy", newYaxisForCircle)
				.attr("cx", newXaxisForCircle)
				.attr("r", newRadiusForCircle)
				.style("fill", self[variablePrefix + 'ColorRange'](errorPercentage));
		}
308

Dave Syer committed
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
		/* 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') {
324
						/* if an array is passed in we'll replace the cached one */
Dave Syer committed
325 326 327 328 329 330
						data = newDataPoint;
					} else {
						// else we just add to the existing one
						data.push({"v":parseFloat(newDataPoint),"t":currentTimeMilliseconds});
					}
				}
331

Dave Syer committed
332
				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
333
					// remove data so we don't keep increasing forever
Dave Syer committed
334
					data.shift();
335 336
				}

Dave Syer committed
337 338 339 340 341
				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;
				}
342

Dave Syer committed
343 344 345 346
				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();
347 348
				}

Dave Syer committed
349
				var xScale = d3.time.scale().domain([new Date(currentTimeMilliseconds-(60*1000*2)), new Date(currentTimeMilliseconds)]).range([0, 140]);
350

Dave Syer committed
351 352 353
				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"
354

Dave Syer committed
355 356
				sparkline = d3.svg.line()
				// assign the X function to plot our line as we wish
357
				.x(function(d,i) {
Dave Syer committed
358 359 360 361 362 363 364
					// 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");
365

Dave Syer committed
366 367
				d3.selectAll(cssTarget).attr("d", sparkline(data));
		}
368

Dave Syer committed
369 370 371
		/* private */ function deleteCircuit(circuitName) {
			$('#CIRCUIT_' + circuitName).remove();
		}
372

Dave Syer committed
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
	};

	// 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});
	};

402

Dave Syer committed
403 404 405 406 407 408 409 410 411 412 413 414
	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});
	};
415

Dave Syer committed
416 417 418 419 420 421 422
	HystrixCommandMonitor.prototype.sortByErrorThenVolume = function() {
		var direction = "desc";
		if(this.sortedBy == 'error_then_volume_desc') {
			direction = 'asc';
		}
		this.sortByErrorThenVolumeInDirection(direction);
	};
423

Dave Syer committed
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
	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') {
495
			this.sortByErrorThenVolumeInDirection('desc');
Dave Syer committed
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
		} 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');
516
		}
Dave Syer committed
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
	};

	// 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);