<commit_before>/**
 * Copyright 2014 Solinea, Inc.
 *
 * Licensed under the Solinea Software License Agreement (goldstone),
 * Version 1.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *     http://www.solinea.com/goldstone/LICENSE.pdf
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author: Alex Jacobs
 */

var UtilizationView = Backbone.View.extend({

    defaults: {
        margin: {
            top: 20,
            right: 40,
            bottom: 25,
            left: 33
        }
    },

    initialize: function(options) {
        this.options = options || {};
        this.defaults = _.clone(this.defaults);
        this.defaults.url = this.collection.url;
        this.defaults.location = options.location;
        this.defaults.width = options.width;

        var ns = this.defaults;
        var self = this;

        this.collection.on('sync', this.update, this);

        ns.mw = ns.width - ns.margin.left - ns.margin.right;
        ns.mh = ns.width - ns.margin.top - ns.margin.bottom;

        ns.formatPercent = d3.format(".0%");

        ns.x = d3.time.scale()
            .range([0, ns.mw]);

        ns.y = d3.scale.linear()
            .range([ns.mh, 0]);

        ns.color = d3.scale.category20();

        ns.xAxis = d3.svg.axis()
            .scale(ns.x)
            .orient("bottom")
            .ticks(4);

        ns.yAxis = d3.svg.axis()
            .scale(ns.y)
            .orient("left")
            .tickFormat(ns.formatPercent);

        ns.area = d3.svg.area()
            .x(function(d) {
                return ns.x(d.date);
            })
            .y0(function(d) {
                return ns.y(d.y0);
            })
            .y1(function(d) {
                return ns.y(d.y0 + d.y);
            });

        ns.stack = d3.layout.stack()
            .values(function(d) {
                return d.values;
            });

        ns.svg = d3.select(ns.location).append("svg")
            .attr("width", ns.mw + ns.margin.left + ns.margin.right)
            .attr("height", ns.mh + ns.margin.top + ns.margin.bottom)
            .append("g")
            .attr("transform", "translate(" + ns.margin.left + "," + ns.margin.top + ")");

        // required in case spinner loading takes
        // longer than chart loading
        ns.spinnerDisplay = 'inline';

        $('').load(function() {
            $(this).appendTo(ns.location).css({
                'position': 'relative',
                'margin-left': (ns.width / 2),
                'margin-top': -(ns.width / 2),
                'display': ns.spinnerDisplay

            });
        });

    },

    update: function() {

        var ns = this.defaults;
        var self = this;

        // sets css for spinner to hidden in case
        // spinner callback resolves
        // after chart data callback
        ns.spinnerDisplay = 'none';
        $(ns.location).find('#spinner').hide();

        var allTheLogs = this.collection.toJSON();

        if (allTheLogs.length === 0) {
            console.log('no data returned');
            return;
        }

        var data = allTheLogs;

        ns.color.domain(d3.keys(data[0]).filter(function(key) {
            return key !== "date";
        }));

        var browsers = ns.stack(ns.color.domain().map(function(name) {
            return {
                name: name,
                values: data.map(function(d) {
                    return {
                        date: d.date,
                        y: d[name] / 100
                    };
                })
            };
        }));

        ns.x.domain(d3.extent(data, function(d) {
            return d.date;
        }));

        var browser = ns.svg.selectAll(".browser")
            .data(browsers)
            .enter().append("g")
            .attr("class", "browser");

        browser.append("path")
            .attr("class", "area")
            .attr("d", function(d) {
                return ns.area(d.values);
            })
            .style("fill", function(d) {
                if(d.name === "Idle"){
                    return "none";
                }
                return ns.color(d.name);
            });

        browser.append("text")
            .datum(function(d) {
                return {
                    name: d.name,
                    value: d.values[d.values.length - 1]
                };
            })
            .attr("transform", function(d) {
                return "translate(" + ns.x(d.value.date) + "," + ns.y(d.value.y0 + d.value.y / 2) + ")";
            })
            .attr("x", 1)
            .style("font-size", ".8em")
            .text(function(d) {
                return d.name;
            });

        ns.svg.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + ns.mh + ")")
            .call(ns.xAxis);

        ns.svg.append("g")
            .attr("class", "y axis")
            .call(ns.yAxis);

    }

});
<commit_msg>Modifies utilization charts to use 'monotone' interpolation.<commit_after>/**
 * Copyright 2014 Solinea, Inc.
 *
 * Licensed under the Solinea Software License Agreement (goldstone),
 * Version 1.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *     http://www.solinea.com/goldstone/LICENSE.pdf
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author: Alex Jacobs
 */

var UtilizationView = Backbone.View.extend({

    defaults: {
        margin: {
            top: 20,
            right: 40,
            bottom: 25,
            left: 33
        }
    },

    initialize: function(options) {
        this.options = options || {};
        this.defaults = _.clone(this.defaults);
        this.defaults.url = this.collection.url;
        this.defaults.location = options.location;
        this.defaults.width = options.width;

        var ns = this.defaults;
        var self = this;

        this.collection.on('sync', this.update, this);

        ns.mw = ns.width - ns.margin.left - ns.margin.right;
        ns.mh = ns.width - ns.margin.top - ns.margin.bottom;

        ns.formatPercent = d3.format(".0%");

        ns.x = d3.time.scale()
            .range([0, ns.mw]);

        ns.y = d3.scale.linear()
            .range([ns.mh, 0]);

        ns.color = d3.scale.category20();

        ns.xAxis = d3.svg.axis()
            .scale(ns.x)
            .orient("bottom")
            .ticks(4);

        ns.yAxis = d3.svg.axis()
            .scale(ns.y)
            .orient("left")
            .tickFormat(ns.formatPercent);

        ns.area = d3.svg.area()
            .interpolate("monotone")
            .x(function(d) {
                return ns.x(d.date);
            })
            .y0(function(d) {
                return ns.y(d.y0);
            })
            .y1(function(d) {
                return ns.y(d.y0 + d.y);
            });

        ns.stack = d3.layout.stack()
            .values(function(d) {
                return d.values;
            });

        ns.svg = d3.select(ns.location).append("svg")
            .attr("width", ns.mw + ns.margin.left + ns.margin.right)
            .attr("height", ns.mh + ns.margin.top + ns.margin.bottom)
            .append("g")
            .attr("transform", "translate(" + ns.margin.left + "," + ns.margin.top + ")");

        // required in case spinner loading takes
        // longer than chart loading
        ns.spinnerDisplay = 'inline';

        $('').load(function() {
            $(this).appendTo(ns.location).css({
                'position': 'relative',
                'margin-left': (ns.width / 2),
                'margin-top': -(ns.width / 2),
                'display': ns.spinnerDisplay

            });
        });

    },

    update: function() {

        var ns = this.defaults;
        var self = this;

        // sets css for spinner to hidden in case
        // spinner callback resolves
        // after chart data callback
        ns.spinnerDisplay = 'none';
        $(ns.location).find('#spinner').hide();

        var allTheLogs = this.collection.toJSON();

        if (allTheLogs.length === 0) {
            console.log('no data returned');
            return;
        }

        var data = allTheLogs;

        ns.color.domain(d3.keys(data[0]).filter(function(key) {
            return key !== "date";
        }));

        var browsers = ns.stack(ns.color.domain().map(function(name) {
            return {
                name: name,
                values: data.map(function(d) {
                    return {
                        date: d.date,
                        y: d[name] / 100
                    };
                })
            };
        }));

        ns.x.domain(d3.extent(data, function(d) {
            return d.date;
        }));

        var browser = ns.svg.selectAll(".browser")
            .data(browsers)
            .enter().append("g")
            .attr("class", "browser");

        browser.append("path")
            .attr("class", "area")
            .attr("d", function(d) {
                return ns.area(d.values);
            })
            .style("fill", function(d) {
                if(d.name === "Idle"){
                    return "none";
                }
                return ns.color(d.name);
            });

        browser.append("text")
            .datum(function(d) {
                return {
                    name: d.name,
                    value: d.values[d.values.length - 1]
                };
            })
            .attr("transform", function(d) {
                return "translate(" + ns.x(d.value.date) + "," + ns.y(d.value.y0 + d.value.y / 2) + ")";
            })
            .attr("x", 1)
            .style("font-size", ".8em")
            .text(function(d) {
                return d.name;
            });

        ns.svg.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + ns.mh + ")")
            .call(ns.xAxis);

        ns.svg.append("g")
            .attr("class", "y axis")
            .call(ns.yAxis);

    }

});
<|endoftext|>


<commit_before>(function () {
   'use strict';

	var http = require('http'),
		os = require('os'),
		cluster = require('cluster'),
		url = require('url'),
		Worker = require(__dirname + '/worker.js'),
		formatTime = require(__dirname + '/formatTime.js'),
		formatMemory = require(__dirname + '/formatMemory.js');

	function Status(config) {
		if (cluster.isMaster && !config) {
			throw new Error('Kardia cannot start - configuration not supplied');
		}
		if (cluster.isMaster && !config.name) {
			throw new Error('Kardia cannot start - service name must be supplied');
		}

		this.config = config;
		config.debug = !!config.debug;

		if (cluster.isMaster && !config.port) {
			this.log('info', 'Kardia starting on default port (12900)');
			config.port = 12900;
		}
		if (cluster.isMaster && !config.host) {
			this.log('info', 'Kardia starting on default host (0.0.0.0)');
			config.host = '0.0.0.0';
		}

		this.startTime = new Date();
		this.values = {};
		this.counters = {};
		this.throughputs = {};
		this.throughputsBuffer = {};
		this.stacks = {};
		this.healthcheck = null;
		this.healthcheckTimeout = null;
		this.workers = {};
		this.eventListeners = {};
		this.startMemory = process.memoryUsage();
		this.fallBehind = 0;
		this.stackConfig = {};

		this.measureFallBehind();

		if (cluster.isMaster && config.healthcheck) {
			this.registerHealthcheck(config.healthcheck);
		}

		return this;
	}

	Status.prototype.measureFallBehind = function() {
		var that = this,
			time = process.hrtime();

		setTimeout(function() {
			var diff = process.hrtime(time);

			that.fallBehind = ((diff[0] - 1) * 1e9 + diff[1]) / 1e6;
			that.measureFallBehind();
		}, 1000);
	};

	Status.prototype.set = function(name, value) {
		if (this.values[name] !== value) {
			this.values[name] = value;
			if (cluster.isWorker && process.send) {
				process.send({ '~kardia': { cmd: 'set', args: [].slice.call(arguments) } });
				return;
			}
		}
	};

	Status.prototype.unset = function(name, value) {
		if (typeof this.values[name] !== 'undefined') {
			delete this.values[name];
			if (cluster.isWorker && process.send) {
				process.send({ '~kardia': { cmd: 'unset', args: [].slice.call(arguments) } });
				return;
			}
		}
	};

	Status.prototype.increment = function(name, value) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'increment', args: [].slice.call(arguments) } });
			return;
		}
		this.counters[name] = (this.counters[name] || 0) + (Number(value) || 1);
	};

	Status.prototype.decrement = function(name, value) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'decrement', args: [].slice.call(arguments) } });
			return;
		}
		this.increment(name, -(Number(value) || 1));
	};

	Status.prototype.throughput = function(name) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'throughput', args: [].slice.call(arguments) } });
			return;
		}

		if (!this.throughputsBuffer[name]) {
			this.throughputsBuffer[name] = 0;
		}

		this.throughputsBuffer[name]++;

		// the throughput calculation interval time in seconds (will likely be configurable in the future):
		if (!this.throughputCalculationStep) {
			this.throughputCalculationStep = 30;
		}

		if (!this.throughputInterval && Object.keys(this.throughputsBuffer).length > 0) {

			// set up the appropriate interval to calculate throughput rates per sec, minute, hour
			this.throughputInterval = setInterval((function() {
				Object.keys(this.throughputsBuffer).forEach((function(key) {
					if (!this.throughputs[key]) {
						this.throughputs[key] = {};
					}
					if (!this.throughputs[key][this.throughputCalculationStep]) {
						this.throughputs[key][this.throughputCalculationStep] = {};
					}
					this.throughputs[key][this.throughputCalculationStep].sec = parseInt((this.throughputsBuffer[key] / this.throughputCalculationStep).toFixed(2), 10);
					this.throughputs[key][this.throughputCalculationStep].min = parseInt((this.throughputsBuffer[key] * (60 / this.throughputCalculationStep)).toFixed(2), 10);
					this.throughputs[key][this.throughputCalculationStep].hour = parseInt((this.throughputsBuffer[key] * (3600 / this.throughputCalculationStep)).toFixed(2), 10);
					this.throughputsBuffer[key] = 0;

				}).bind(this));
				this.emit('throughputIntervalRun');

			}).bind(this), 1000 * this.throughputCalculationStep);
		}
	};

	Status.prototype.clearThroughput = function(name) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'clearThroughput', args: [].slice.call(arguments) } });
			return;
		}
		delete this.throughputsBuffer[name];
		delete this.throughput[name];

		if (Object.keys(this.throughputInterval).length === 0 && this.throughputInterval) {
			clearInterval(this.throughputInterval);
		}
	};

	Status.prototype.startStack = function(name, size) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'startStack', args: [].slice.call(arguments) } });
			return;
		}
		this.stacks[name] = [];
		this.stackConfig[name] = { size: Number(size) || 15 };
	};

	Status.prototype.stack = function(name, value) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'stack', args: [].slice.call(arguments) } });
			return;
		}
		if (!this.stacks[name]) this.startStack(name);
		this.stacks[name].unshift({ time: new Date().toISOString(), value: value });
		this.stacks[name] = this.stacks[name].splice(0, this.stackConfig[name].size);
	};

	Status.prototype.stopStack = function(name) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'stopStack', args: [].slice.call(arguments) } });
			return;
		}
		delete this.stacks[name];
		delete this.stackConfig[name];
	};

	Status.prototype.addWorker = function(worker) {
		if (!worker || !worker.process || !worker.process.pid || !worker.id) {
			throw new Error('PID and ID are required to add a worker process to Kardia');
		}

		this.workers[worker.process.pid] = new Worker(worker, this);

		return this.workers[worker.process.pid];
	};

	Status.prototype.removeWorker = function(pid) {
		if (!pid) {
			throw new Error('PID is required to remove a worker process from Kardia');
		}

		delete this.workers[pid];
	};

	Status.prototype.registerHealthcheck = function(options) {
		if (!cluster.isMaster) {
			throw new Error('Kardia health checks can only be registered on the master process as of now.');
		}

		if (typeof options === 'function') {
			options = {
				handler: options,
				timeout: 10
			};
		}

		this.healthcheck = options.handler;
		this.healthcheckTimeout = options.timeout;
	};

	Status.prototype.getConsulHealthcheck = function(options) {
		var host = (this.config.host === '0.0.0.0') ? os.hostname() : this.config.host;

		var consulParams = {};
		consulParams.interval = options && options.interval || '10s';
		consulParams.notes = options && options.notes || this.config.name + ' Kardia health check';
		consulParams.http = 'http://'+host+':'+this.config.port+'/health';
		consulParams.service_id = options && options.service_id || this.config.name;

		return consulParams;
	};

	Status.prototype._onClusterFork = function(worker) {
		this.addWorker(worker);
	};

	Status.prototype._onClusterExit = function(worker) {
		this.removeWorker(worker.process.pid);
	};

	Status.prototype.startServer = function() {
		this.onClusterFork = this._onClusterFork.bind(this);
		this.onClusterExit = this._onClusterExit.bind(this);

		cluster.on('fork', this.onClusterFork);
		cluster.on('exit', this.onClusterExit);

		this.server = http.createServer((function (req, res) {
			this.emit('serverRequest', { req: req });

			if (req.url.substr(0, 7) === '/health') {
				this.serveHealthcheckRequest(req, res);
			} else {
				this.serveStatusRequest(req, res);
			}
		}).bind(this));

		this.server.listen(this.config.port, this.config.host, (function() {
			this.log('info', 'Kardia listening on ' + this.config.host + ':' + this.config.port);
			this.emit('serverListening');
		}).bind(this));

		this.server.on('error', (function(err) {
			throw new Error('Kardia error + ' + err.stack);
			this.emit('serverError', err);
		}).bind(this));
	};

	Status.prototype.serveStatusRequest = function(req, res) {
		res.writeHead(200, {'Content-Type': 'application/json'});
		res.end(JSON.stringify(this.generateStatus(req), false, 4) + '\n');
	};

	Status.prototype.serveHealthcheckRequest = function(req, res) {
		var callbackFired = false,
			checkTimeout;

		var callback = function(err) {
			if (callbackFired) {
				return;
			}
			callbackFired = true;

			if (checkTimeout) {
				clearTimeout(checkTimeout);
			}

			if (err) {
				checkFailed(err);
			} else {
				checkSucceeded();
			}
		};

		var checkSucceeded = function() {
			res.writeHead(200, {'Content-Type': 'application/json'});
			res.end(JSON.stringify({
				success: true
			}, false, 4) + '\n');
		};

		var checkFailed = function(err) {
			res.writeHead(500);
			res.end(JSON.stringify({
				success: false,
				error: err.toString()
			}, null, 4) + '\n');
		};

		checkTimeout = function() {
			callback(new Error('Health check timed out (> ' + this.healthcheckTimeout + ' sec)'));
		};

		if (this.healthcheckTimeout > 0) {
			checkTimeout = setTimeout(checkTimeout.bind(this), this.healthcheckTimeout * 1000);
		}

		// execute the health check
		if (this.healthcheck && typeof this.healthcheck === 'function') {
			this.healthcheck(callback, this.generateStatus(req));
		} else {
			callback(new Error('Health check not registered'));
		}
	};

	Status.prototype.stopServer = function() {
		if (!cluster.isMaster) {
			return false;
		}

		try {
			this.server.close();
			cluster.removeListener('fork', this.onClusterFork);
			cluster.removeListener('exit', this.onClusterExit);
		} catch (e) {}

		this.emit('serverStopped');

		return true;
	};

	Status.prototype.generateStatus = function(req) {
		var date = new Date(),
			workersData = [];

		Object.keys(this.workers).forEach((function(pid) {
			workersData.push(this.workers[pid].generateStatus());
		}).bind(this));

		var resp = {
			service: this.config.name,
			pid: process.pid,
			env: process.env.NODE_ENV || 'development',
			uptime: Math.floor((date.getTime() - this.startTime.getTime())/1000),
			uptime_formatted: formatTime(Math.floor((date.getTime() - this.startTime.getTime())/1000)),
			startTime: this.startTime.toISOString(),
			curTime: date.toISOString(),
			uid: process.getuid ? process.getuid() : null,
			gid: process.getgid ? process.getgid() : null,
			values: this.values,
			counters: this.counters,
			throughput: this.throughputs,
			stacks: this.stacks,
			workers: workersData,
			remoteAddress: (req && req.socket && req.socket.remoteAddress ? req.socket.remoteAddress : false),
			network: os.networkInterfaces(),
			hostname: os.hostname(),
			memory: formatMemory(process.memoryUsage(), this.startMemory),
			fallBehind: this.fallBehind,
			os: {
				type: os.type(),
				platform: os.platform(),
				arch: os.arch(),
				release: os.release(),
				uptime: os.uptime(),
				loadavg: os.loadavg(),
				totalmem: os.totalmem(),
				freemem: os.freemem()
			},
			config: this.config
		};

		return resp;
	};

	Status.prototype.log = function(level, msg) {
		switch (level) {
			case 'error':
				if (this.config) process.stderr.write(new Date().toString() + ': ' + msg.toString() + '\n');
				throw new Error(msg.toString());
			default:
				if (this.config && this.config.debug) process.stdout.write(new Date().toString() + ': ' + (msg ? msg.toString() : msg) + '\n');
				break;
		}
	};

	Status.prototype.emit = function(eventName, data) {
		if (this.eventListeners[eventName]) {
			this.eventListeners[eventName].forEach(function(listener) {
				listener(data);
			});
		}
	};

	Status.prototype.on = function(eventName, handler) {
		if (!this.eventListeners[eventName]) {
			this.eventListeners[eventName] = [];
		}

		this.eventListeners[eventName].push(handler);

		return true;
	};

	Status.prototype.removeListener = function(eventName, handler) {
		if (this.eventListeners[eventName]) {
			this.eventListeners[eventName].splice(this.eventListeners[eventName].indexOf(handler));
		}

		return true;
	};

	Status.prototype.start = function() {
		// dummy wrapper for catching subsequent start calls made by the underlying service using Kardia.
	};

	module.exports = Status;
}());
<commit_msg>code formatting<commit_after>(function () {
   'use strict';

	var http = require('http'),
		os = require('os'),
		cluster = require('cluster'),
		url = require('url'),
		Worker = require(__dirname + '/worker.js'),
		formatTime = require(__dirname + '/formatTime.js'),
		formatMemory = require(__dirname + '/formatMemory.js');

	function Status(config) {
		if (cluster.isMaster && !config) {
			throw new Error('Kardia cannot start - configuration not supplied');
		}
		if (cluster.isMaster && !config.name) {
			throw new Error('Kardia cannot start - service name must be supplied');
		}

		this.config = config;
		config.debug = !!config.debug;

		if (cluster.isMaster && !config.port) {
			this.log('info', 'Kardia starting on default port (12900)');
			config.port = 12900;
		}
		if (cluster.isMaster && !config.host) {
			this.log('info', 'Kardia starting on default host (0.0.0.0)');
			config.host = '0.0.0.0';
		}

		this.startTime = new Date();
		this.values = {};
		this.counters = {};
		this.throughputs = {};
		this.throughputsBuffer = {};
		this.stacks = {};
		this.healthcheck = null;
		this.healthcheckTimeout = null;
		this.workers = {};
		this.eventListeners = {};
		this.startMemory = process.memoryUsage();
		this.fallBehind = 0;
		this.stackConfig = {};

		this.measureFallBehind();

		if (cluster.isMaster && config.healthcheck) {
			this.registerHealthcheck(config.healthcheck);
		}

		return this;
	}

	Status.prototype.measureFallBehind = function() {
		var that = this,
			time = process.hrtime();

		setTimeout(function() {
			var diff = process.hrtime(time);

			that.fallBehind = ((diff[0] - 1) * 1e9 + diff[1]) / 1e6;
			that.measureFallBehind();
		}, 1000);
	};

	Status.prototype.set = function(name, value) {
		if (this.values[name] !== value) {
			this.values[name] = value;
			if (cluster.isWorker && process.send) {
				process.send({ '~kardia': { cmd: 'set', args: [].slice.call(arguments) } });
				return;
			}
		}
	};

	Status.prototype.unset = function(name, value) {
		if (typeof this.values[name] !== 'undefined') {
			delete this.values[name];
			if (cluster.isWorker && process.send) {
				process.send({ '~kardia': { cmd: 'unset', args: [].slice.call(arguments) } });
				return;
			}
		}
	};

	Status.prototype.increment = function(name, value) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'increment', args: [].slice.call(arguments) } });
			return;
		}
		this.counters[name] = (this.counters[name] || 0) + (Number(value) || 1);
	};

	Status.prototype.decrement = function(name, value) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'decrement', args: [].slice.call(arguments) } });
			return;
		}
		this.increment(name, -(Number(value) || 1));
	};

	Status.prototype.throughput = function(name) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'throughput', args: [].slice.call(arguments) } });
			return;
		}

		if (!this.throughputsBuffer[name]) {
			this.throughputsBuffer[name] = 0;
		}

		this.throughputsBuffer[name]++;

		// the throughput calculation interval time in seconds (will likely be configurable in the future):
		if (!this.throughputCalculationStep) {
			this.throughputCalculationStep = 30;
		}

		if (!this.throughputInterval && Object.keys(this.throughputsBuffer).length > 0) {

			// set up the appropriate interval to calculate throughput rates per sec, minute, hour
			this.throughputInterval = setInterval((function() {
				Object.keys(this.throughputsBuffer).forEach((function(key) {
					if (!this.throughputs[key]) {
						this.throughputs[key] = {};
					}
					if (!this.throughputs[key][this.throughputCalculationStep]) {
						this.throughputs[key][this.throughputCalculationStep] = {};
					}
					var x = this.throughputs[key][this.throughputCalculationStep];
					x.sec = parseInt((this.throughputsBuffer[key] / this.throughputCalculationStep).toFixed(2), 10);
					x.min = parseInt((this.throughputsBuffer[key] * (60 / this.throughputCalculationStep)).toFixed(2), 10);
					x.hour = parseInt((this.throughputsBuffer[key] * (3600 / this.throughputCalculationStep)).toFixed(2), 10);
					this.throughputsBuffer[key] = 0;

				}).bind(this));
				this.emit('throughputIntervalRun');

			}).bind(this), 1000 * this.throughputCalculationStep);
		}
	};

	Status.prototype.clearThroughput = function(name) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'clearThroughput', args: [].slice.call(arguments) } });
			return;
		}
		delete this.throughputsBuffer[name];
		delete this.throughput[name];

		if (Object.keys(this.throughputInterval).length === 0 && this.throughputInterval) {
			clearInterval(this.throughputInterval);
		}
	};

	Status.prototype.startStack = function(name, size) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'startStack', args: [].slice.call(arguments) } });
			return;
		}
		this.stacks[name] = [];
		this.stackConfig[name] = { size: Number(size) || 15 };
	};

	Status.prototype.stack = function(name, value) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'stack', args: [].slice.call(arguments) } });
			return;
		}
		if (!this.stacks[name]) this.startStack(name);
		this.stacks[name].unshift({ time: new Date().toISOString(), value: value });
		this.stacks[name] = this.stacks[name].splice(0, this.stackConfig[name].size);
	};

	Status.prototype.stopStack = function(name) {
		if (cluster.isWorker && process.send) {
			process.send({ '~kardia': { cmd: 'stopStack', args: [].slice.call(arguments) } });
			return;
		}
		delete this.stacks[name];
		delete this.stackConfig[name];
	};

	Status.prototype.addWorker = function(worker) {
		if (!worker || !worker.process || !worker.process.pid || !worker.id) {
			throw new Error('PID and ID are required to add a worker process to Kardia');
		}

		this.workers[worker.process.pid] = new Worker(worker, this);

		return this.workers[worker.process.pid];
	};

	Status.prototype.removeWorker = function(pid) {
		if (!pid) {
			throw new Error('PID is required to remove a worker process from Kardia');
		}

		delete this.workers[pid];
	};

	Status.prototype.registerHealthcheck = function(options) {
		if (!cluster.isMaster) {
			throw new Error('Kardia health checks can only be registered on the master process as of now.');
		}

		if (typeof options === 'function') {
			options = {
				handler: options,
				timeout: 10
			};
		}

		this.healthcheck = options.handler;
		this.healthcheckTimeout = options.timeout;
	};

	Status.prototype.getConsulHealthcheck = function(options) {
		var host = (this.config.host === '0.0.0.0') ? os.hostname() : this.config.host;

		var consulParams = {};
		consulParams.interval = options && options.interval || '10s';
		consulParams.notes = options && options.notes || this.config.name + ' Kardia health check';
		consulParams.http = 'http://'+host+':'+this.config.port+'/health';
		consulParams.service_id = options && options.service_id || this.config.name;

		return consulParams;
	};

	Status.prototype._onClusterFork = function(worker) {
		this.addWorker(worker);
	};

	Status.prototype._onClusterExit = function(worker) {
		this.removeWorker(worker.process.pid);
	};

	Status.prototype.startServer = function() {
		this.onClusterFork = this._onClusterFork.bind(this);
		this.onClusterExit = this._onClusterExit.bind(this);

		cluster.on('fork', this.onClusterFork);
		cluster.on('exit', this.onClusterExit);

		this.server = http.createServer((function (req, res) {
			this.emit('serverRequest', { req: req });

			if (req.url.substr(0, 7) === '/health') {
				this.serveHealthcheckRequest(req, res);
			} else {
				this.serveStatusRequest(req, res);
			}
		}).bind(this));

		this.server.listen(this.config.port, this.config.host, (function() {
			this.log('info', 'Kardia listening on ' + this.config.host + ':' + this.config.port);
			this.emit('serverListening');
		}).bind(this));

		this.server.on('error', (function(err) {
			this.emit('serverError', err);
			throw new Error('Kardia error + ' + err.stack);
		}).bind(this));
	};

	Status.prototype.serveStatusRequest = function(req, res) {
		res.writeHead(200, {'Content-Type': 'application/json'});
		res.end(JSON.stringify(this.generateStatus(req), false, 4) + '\n');
	};

	Status.prototype.serveHealthcheckRequest = function(req, res) {
		var callbackFired = false,
			checkTimeout;

		var callback = function(err) {
			if (callbackFired) {
				return;
			}
			callbackFired = true;

			if (checkTimeout) {
				clearTimeout(checkTimeout);
			}

			if (err) {
				checkFailed(err);
			} else {
				checkSucceeded();
			}
		};

		var checkSucceeded = function() {
			res.writeHead(200, {'Content-Type': 'application/json'});
			res.end(JSON.stringify({
				success: true
			}, false, 4) + '\n');
		};

		var checkFailed = function(err) {
			res.writeHead(500);
			res.end(JSON.stringify({
				success: false,
				error: err.toString()
			}, null, 4) + '\n');
		};

		checkTimeout = function() {
			callback(new Error('Health check timed out (> ' + this.healthcheckTimeout + ' sec)'));
		};

		if (this.healthcheckTimeout > 0) {
			checkTimeout = setTimeout(checkTimeout.bind(this), this.healthcheckTimeout * 1000);
		}

		// execute the health check
		if (this.healthcheck && typeof this.healthcheck === 'function') {
			this.healthcheck(callback, this.generateStatus(req));
		} else {
			callback(new Error('Health check not registered'));
		}
	};

	Status.prototype.stopServer = function() {
		if (!cluster.isMaster) {
			return false;
		}

		try {
			this.server.close();
			cluster.removeListener('fork', this.onClusterFork);
			cluster.removeListener('exit', this.onClusterExit);
		} catch (e) {}

		this.emit('serverStopped');

		return true;
	};

	Status.prototype.generateStatus = function(req) {
		var date = new Date(),
			workersData = [];

		Object.keys(this.workers).forEach((function(pid) {
			workersData.push(this.workers[pid].generateStatus());
		}).bind(this));

		var resp = {
			service: this.config.name,
			pid: process.pid,
			env: process.env.NODE_ENV || 'development',
			uptime: Math.floor((date.getTime() - this.startTime.getTime())/1000),
			uptime_formatted: formatTime(Math.floor((date.getTime() - this.startTime.getTime())/1000)),
			startTime: this.startTime.toISOString(),
			curTime: date.toISOString(),
			uid: process.getuid ? process.getuid() : null,
			gid: process.getgid ? process.getgid() : null,
			values: this.values,
			counters: this.counters,
			throughput: this.throughputs,
			stacks: this.stacks,
			workers: workersData,
			remoteAddress: (req && req.socket && req.socket.remoteAddress ? req.socket.remoteAddress : false),
			network: os.networkInterfaces(),
			hostname: os.hostname(),
			memory: formatMemory(process.memoryUsage(), this.startMemory),
			fallBehind: this.fallBehind,
			os: {
				type: os.type(),
				platform: os.platform(),
				arch: os.arch(),
				release: os.release(),
				uptime: os.uptime(),
				loadavg: os.loadavg(),
				totalmem: os.totalmem(),
				freemem: os.freemem()
			},
			config: this.config
		};

		return resp;
	};

	Status.prototype.log = function(level, msg) {
		switch (level) {
			case 'error':
				if (this.config) process.stderr.write(new Date().toString() + ': ' + msg.toString() + '\n');
				throw new Error(msg.toString());
			default:
				if (this.config && this.config.debug) process.stdout.write(new Date().toString() + ': ' + (msg ? msg.toString() : msg) + '\n');
				break;
		}
	};

	Status.prototype.emit = function(eventName, data) {
		if (this.eventListeners[eventName]) {
			this.eventListeners[eventName].forEach(function(listener) {
				listener(data);
			});
		}
	};

	Status.prototype.on = function(eventName, handler) {
		if (!this.eventListeners[eventName]) {
			this.eventListeners[eventName] = [];
		}

		this.eventListeners[eventName].push(handler);

		return true;
	};

	Status.prototype.removeListener = function(eventName, handler) {
		if (this.eventListeners[eventName]) {
			this.eventListeners[eventName].splice(this.eventListeners[eventName].indexOf(handler));
		}

		return true;
	};

	Status.prototype.start = function() {
		// dummy wrapper for catching subsequent start calls made by the underlying service using Kardia.
	};

	module.exports = Status;
}());
<|endoftext|>


<commit_before>'use strict'

var Duplex = require('stream').Duplex
var inherits = require('util').inherits
var PassThrough = require('stream').PassThrough
var timers = require('timers')
var winston = require('winston')
var winstonWrapper = require('winston-meta-wrapper')

function ProxyStream () {
  if (!(this instanceof ProxyStream)) {
    return new ProxyStream()
  }
  // logging
  this._log = winstonWrapper(winston)
  this._log.addMeta({
    module: '1tp:transports:streams:proxy'
  })
  // init duplex
  Duplex.call(this, ProxyStream.DEFAULTS)
  // create inbound and outbound streams
  this._outboundPassThrough = new PassThrough()
  this._inboundPassThrough = new PassThrough()
  // listen to _inboundPassThrough
  var self = this
  this._inboundPassThrough
    .on('readable', function () {
      self._readFromInboundPassThrough()
    })
    .on('end', function () {
      self.push(null) // EOF
    })
  // done
  this._log.debug('created proxy stream.')
}

ProxyStream.DEFAULTS = {
  allowHalfOpen: true,
  readableObjectMode: false,
  writableObjectMode: false
}

inherits(ProxyStream, Duplex)

ProxyStream.prototype.connectStream = function (stream) {
  this._connectedStream = stream
  this._outboundPassThrough.on('error', this._error).pipe(this._connectedStream).on('error', this._error)
  this._connectedStream.on('error', this._error).pipe(this._inboundPassThrough).on('error', this._error)
}

ProxyStream.prototype._read = function (size) {
  this._unrefTimer()
  this._readFromInboundPassThrough(size)
}

ProxyStream.prototype._readFromInboundPassThrough = function (size) {
  var chunk
  while ((chunk = this._inboundPassThrough.read(size)) !== null) {
    // if push returns false, stop writing
    if (!this.push(chunk)) {
      break
    }
  }
}

ProxyStream.prototype._write = function (chunk, encoding, done) {
  if (!this._connectedStream) {
    var noStreamError = 'no connected stream to write data to.'
    this._log.error(noStreamError)
    throw new Error(noStreamError)
  }
  this._unrefTimer()
  this._outboundPassThrough.write(chunk, encoding, done)
}

ProxyStream.prototype.end = function () {
  this._outboundPassThrough.end()
  var self = this
  process.nextTick(function () {
    self._end()
  })
}

ProxyStream.prototype._end = function () {
  // end writestream
  ProxyStream.super_.prototype.end.call(this)
}

// TODO: implement destroy functionality
ProxyStream.prototype._destroy = function () {
  for (var s = this; s !== null; s = s._parent) {
    timers.unenroll(s)
  }
}

/** timer usage is heavily inspired by https://github.com/nodejs/node/blob/master/lib/net.js */
ProxyStream.prototype.setTimeout = function (msecs, callback) {
  if (msecs === 0) {
    timers.unenroll(this)
    if (callback) {
      this.removeListener('timeout', callback)
    }
  } else {
    timers.enroll(this, msecs)
    timers._unrefActive(this)
    if (callback) {
      this.once('timeout', callback)
    }
  }
  return this
}

ProxyStream.prototype._onTimeout = function () {
  this._log.debug('timeout')
  this.emit('timeout')
}

ProxyStream.prototype._unrefTimer = function unrefTimer () {
  timers._unrefActive(this)
}

module.exports = ProxyStream
<commit_msg>fixed bug introduced by commit ccb46dc<commit_after>'use strict'

var Duplex = require('stream').Duplex
var inherits = require('util').inherits
var myUtils = require('./utils')
var PassThrough = require('stream').PassThrough
var timers = require('timers')
var winston = require('winston')
var winstonWrapper = require('winston-meta-wrapper')

function ProxyStream () {
  if (!(this instanceof ProxyStream)) {
    return new ProxyStream()
  }
  // logging
  this._log = winstonWrapper(winston)
  this._log.addMeta({
    module: '1tp:transports:streams:proxy'
  })
  // init duplex
  Duplex.call(this, ProxyStream.DEFAULTS)
  // create inbound and outbound streams
  this._outboundPassThrough = new PassThrough()
  this._inboundPassThrough = new PassThrough()
  // listen to _inboundPassThrough
  var self = this
  this._inboundPassThrough
    .on('readable', function () {
      self._readFromInboundPassThrough()
    })
    .on('end', function () {
      self.push(null) // EOF
    })
  // register _error handler
  myUtils.mixinEventEmitterErrorFunction(this)
  // done
  this._log.debug('created proxy stream.')
}

ProxyStream.DEFAULTS = {
  allowHalfOpen: true,
  readableObjectMode: false,
  writableObjectMode: false
}

inherits(ProxyStream, Duplex)

ProxyStream.prototype.connectStream = function (stream) {
  this._connectedStream = stream
  this._outboundPassThrough.on('error', this._error).pipe(this._connectedStream).on('error', this._error)
  this._connectedStream.on('error', this._error).pipe(this._inboundPassThrough).on('error', this._error)
}

ProxyStream.prototype._read = function (size) {
  this._unrefTimer()
  this._readFromInboundPassThrough(size)
}

ProxyStream.prototype._readFromInboundPassThrough = function (size) {
  var chunk
  while ((chunk = this._inboundPassThrough.read(size)) !== null) {
    // if push returns false, stop writing
    if (!this.push(chunk)) {
      break
    }
  }
}

ProxyStream.prototype._write = function (chunk, encoding, done) {
  if (!this._connectedStream) {
    var noStreamError = 'no connected stream to write data to.'
    this._log.error(noStreamError)
    throw new Error(noStreamError)
  }
  this._unrefTimer()
  this._outboundPassThrough.write(chunk, encoding, done)
}

ProxyStream.prototype.end = function () {
  this._outboundPassThrough.end()
  var self = this
  process.nextTick(function () {
    self._end()
  })
}

ProxyStream.prototype._end = function () {
  // end writestream
  ProxyStream.super_.prototype.end.call(this)
}

// TODO: implement destroy functionality
ProxyStream.prototype._destroy = function () {
  for (var s = this; s !== null; s = s._parent) {
    timers.unenroll(s)
  }
}

/** timer usage is heavily inspired by https://github.com/nodejs/node/blob/master/lib/net.js */
ProxyStream.prototype.setTimeout = function (msecs, callback) {
  if (msecs === 0) {
    timers.unenroll(this)
    if (callback) {
      this.removeListener('timeout', callback)
    }
  } else {
    timers.enroll(this, msecs)
    timers._unrefActive(this)
    if (callback) {
      this.once('timeout', callback)
    }
  }
  return this
}

ProxyStream.prototype._onTimeout = function () {
  this._log.debug('timeout')
  this.emit('timeout')
}

ProxyStream.prototype._unrefTimer = function unrefTimer () {
  timers._unrefActive(this)
}

module.exports = ProxyStream
<|endoftext|>


<commit_before>/**
 * @copyright Maichong Software Ltd. 2016 http://maichong.it
 * @date 2016-04-10
 * @author Liang <liang@maichong.it>
 */

'use strict';

const EventEmitter = require('events');
const Segment = require('./segment');
const Packet = require('./packet');
const CMD = require('./cmd');

const CONFIRM = 10;

const LBOUND = 50; //RTO 最小值
const UBOUND = 500;  //RTO 最大值

class Stream extends EventEmitter {
  constructor(id, connection) {
    super();
    this.id = id;
    this.connection = connection;
    this.sentMaxSegmentId = 0; //发送方,发送的最大Segment ID
    this.successMaxSegmentId = 0; //发送方,确认发送成功的最大Segment ID
    this.receivedMaxSegmentId = 0; //接收方,已经收到的最大Segment ID
    this.confirmedMaxSegmentId = 0; //接收方,已经确认的最大Segment ID
    this.received = {}; // 接收的 Segment 缓冲
    this.receivedLength = 0;
    this.confirmTimer = 0;//确认Timer
    this.lastAlive = Date.now();
    this.sent = {};//发送端,已经发送,待确认的Segment
    this.sentLength = 0;
    this.bytesRead = 0;
    this.bytesWritten = 0;
    this.remoteAddress = connection.remoteAddress;
    this.remotePort = connection.remotePort;
    this._writeQueue = [];
    this.writeTimer = 0;
    this.resentPacketCount = 0;
    this.resentPacketCountAuto = 0;
    this.resentPacketCountConfirm = 0;
    this._resend = {}; //重新发送列表
    this.closed = false;
    this.srtt = 100;

    this.aliveTimer = setInterval(() => {
      let now = Date.now();
      if (now - this.lastAlive > 300000) {
        //5分钟不活跃
        this.close();
        return;
      }
      if (this.receivedLength && now - this.lastAlive > 5000) {
        //5秒内不活跃,并且有需要重发的包
        //请求重发
        this._confirm();
        return;
      }
    }, 5000);

    this.resendTimer = setInterval(() => {
      let now = Date.now();
      let rto = this.connection.rto * 10;
      for (let i in this.sent) {
        let segment = this.sent[i];
        //TODO RTT
        if (now - segment.lastSent > rto) {
          if (this._resend[segment.id]) {
            continue;
          }
          segment.lastSent = now;
          segment.sack = 0;
          this._resend[segment.id] = segment;
          this.resentPacketCountAuto++;
        }
      }
      if (!this.confirmTimer && this.receivedLength) {
        this.confirmTimer = setTimeout(() => {
          this.confirmTimer = 0;
          this._confirm();
        }, 1);
      }

      for (let id in this._resend) {
        let segment = this._resend[id];
        delete this._resend[id];
        if (segment.a && !segment.a.onBus) {
          this.connection.send(segment.a);
          this.resentPacketCount++;
        }
        if (segment.b && !segment.b.onBus) {
          this.connection.send(segment.b);
          this.resentPacketCount++;
        }
        if (segment.c && !segment.c.onBus) {
          this.connection.send(segment.c);
          this.resentPacketCount++;
        }
        if (segment.p && !segment.p.onBus) {
          this.connection.send(segment.p);
          this.resentPacketCount++;
        }
        segment.lastSent = now;
        segment.sack = 0;
      }
    }, 10);
  }

  close() {
    //console.log('stream', this.id, 'close');
    if (this.connection) {
      this.emit('close');
      let buffer = new Buffer(4);
      buffer.writeUInt8(CMD.CLOSE_STREAM);
      this.idBuffer.copy(buffer, 1);
      let packet = new Packet(buffer);
      this.connection._send(packet);
    }
    this.closed = true;
    delete this._idBuffer;
    delete this.received;
    delete this.sent;
    delete this.connection;
    delete this._writeQueue;
    if (this.confirmTimer) {
      clearTimeout(this.confirmTimer);
    }
    clearInterval(this.aliveTimer);
    clearInterval(this.resendTimer);
  }

  get idBuffer() {
    if (!this._idBuffer) {
      this._idBuffer = Stream.idToBuffer(this.id);
    }
    return this._idBuffer;
  }

  static idToBuffer(id) {
    let tmp = new Buffer(4);
    let buffer = new Buffer(3);
    tmp.writeInt32LE(id, 0);
    tmp.copy(buffer, 0, 0, 3);
    return buffer;
  }

  static bufferToId(buffer) {
    let tmp = new Buffer(4);
    tmp.fill(0, 3);
    buffer.copy(tmp);
    return tmp.readInt32LE(0);
  }

  /**
   * @param data
   * @param [encoding]
   * @param [callback]
   */
  write(data, encoding, callback) {
    if (!data || !data.length) {
      return;
    }
    if (typeof data === 'string') {
      data = new Buffer(data, encoding);
    }

    //如果一次写入的数据大于单个包最大数据
    //拆开写入多次
    if (data.length > 4350) {
      for (let start = 0; start < data.length; start += 4350) {
        let end = start + 4350;
        if (end > data.length) {
          end = data.length;
        }
        this._writeQueue.push(data.slice(start, end));
      }
    } else {
      this._writeQueue.push(data);
    }
    if (!this.writeTimer) {
      this._write();
    }
  }

  _write() {
    if (this.closed) {
      return;
    }
    this.writeTimer = 0;
    if (!this._writeQueue.length) {
      return;
    }
    let rto = this.connection.rto;
    if (this.sentLength > rto * 5) {
      this.writeTimer = setTimeout(() => this._write(), 2);
      return;
    }
    let data = this._writeQueue.shift();
    if (!data) {
      return;
    }
    this.bytesWritten += data.length;
    this.sentMaxSegmentId++;
    let segment = new Segment(this.sentMaxSegmentId, this, data);
    segment.pack();
    segment.lastSent = Date.now();
    this.connection.send(segment.a);
    this.connection.send(segment.b);
    this.connection.send(segment.c);
    //this.connection.send(segment.p);
    this.sent[segment.id] = segment;
    this.sentLength++;
    if (this.sentLength < this.srtt * 2) {
      this._write();
    } else {
      this.writeTimer = setTimeout(() => this._write(), parseInt(rto / 100));
    }
  }

  /**
   * 当收到Packet
   * @param packet
   */
  receive(packet) {
    this.lastAlive = Date.now();

    if (packet.cmd === CMD.CONFIRM) {
      this._receiveSACK(packet);
    }
  }

  _receiveSACK(packet) {
    const now = Date.now();
    let segmentId = packet.segmentId;
    for (let i = this.successMaxSegmentId + 1; i <= packet.segmentId; i++) {
      if (this.sent[i]) {
        let time = now - this.sent[i].createdAt;
        this.srtt = 0.9 * this.srtt + 0.1 * time;
        this.srtt = Math.min(UBOUND, Math.max(LBOUND, this.srtt));
        this.sentLength--;
        delete this.sent[i];
        delete this._resend[i];
        //console.log('receive.remove', i);
      }
    }
    //console.log('confirm', segmentId - this.successMaxSegmentId);
    this.successMaxSegmentId = segmentId;

    //SACK
    let buffer = packet.buffer;
    let index = 8;
    let lastSegmentId = segmentId;
    while (buffer.length > index) {
      let offset = buffer.readUInt8(index);
      if (!offset) {
        throw new Error('offset ' + offset);
      }
      let code = buffer.readUInt8(index + 1);
      index += 2;
      let id = segmentId + offset;
      let segment = this.sent[id];
      if (!segment) {
        console.error('error , can not resend ', id);
        this.close();
        return;
      }
      while (id - lastSegmentId > 1) {
        lastSegmentId++;
        //中间有一些已经接收到了
        if (this.sent[lastSegmentId]) {
          let time = now - this.sent[lastSegmentId].createdAt;
          this.srtt = 0.9 * this.srtt + 0.1 * time;
          this.srtt = Math.min(UBOUND, Math.max(LBOUND, this.srtt));
          this.sentLength--;
          delete this.sent[lastSegmentId];
          //console.log('remove', lastSegmentId);
        }
        //console.log(lastSegmentId, id);
      }
      lastSegmentId = id;

      if (segment.a && code % CMD.DATA_A !== 0) {
        delete segment.a;
        segment.count--;
      }

      if (segment.b && code % CMD.DATA_B !== 0) {
        delete segment.b;
        segment.count--;
      }

      if (segment.c && code % CMD.DATA_B !== 0) {
        delete segment.c;
        segment.count--;
      }

      if (segment.p && code % CMD.DATA_RESEND_P !== 0) {
        delete segment.p;
        segment.count--;
      }

      if (this._resend[segment.id]) {
        continue;
      }
      if (now - segment.lastSack < this.connection.rto) {
        continue;
      }
      if (segment.sack < CONFIRM - 1) {
        segment.lastSack = now;
        segment.sack++;
        continue;
      }
      this._resend[segment.id] = segment;
      segment.lastSent = now;
      segment.lastSack = now;
      segment.sack = 0;
      this.resentPacketCountConfirm++;
    }
  }

  /**
   * 当收到数据Packet
   * @param packet
   */
  receiveData(packet) {
    this.lastAlive = Date.now();
    let segmentId = packet.segmentId;
    if (segmentId < this.confirmedMaxSegmentId) {
      //已经确认,重复接收
      return;
    }

    if (segmentId - this.confirmedMaxSegmentId > 10000) {
      //segmentId太大,不接受
      return;
    }

    let segment = this.received[segmentId];
    if (!segment) {
      segment = this.received[segmentId] = new Segment(segmentId, this, null);
    }

    let cmd = packet.cmd;
    if (!segment.a && cmd % CMD.DATA_A === 0) {
      //a
      segment.a = packet;
      segment.count++;
    } else if (!segment.b && cmd % CMD.DATA_B === 0) {
      //b
      segment.b = packet;
      segment.count++;
    } else if (!segment.c && cmd % CMD.DATA_C === 0) {
      //c
      segment.c = packet;
      segment.count++;
    } else if (!segment.p && cmd % CMD.DATA_P === 0) {
      //p
      segment.p = packet;
      segment.count++;
    } else {
      //没有能够识别包,或重复接收
      if (!segment.count) {
        delete this.received[segmentId];
      }
      return;
    }

    if (segmentId > this.receivedMaxSegmentId) {
      this.receivedMaxSegmentId = segmentId;
    }
    this.receivedLength = this.receivedMaxSegmentId - this.confirmedMaxSegmentId;

    if (segment.count >= 3) {
      setImmediate(() => this._readData());
    }
  }

  _readData() {
    let i = this.confirmedMaxSegmentId;
    let buffers = [];
    while (true) {
      let segment = this.received[i + 1];
      if (!segment || segment.count < 3) {
        break;
      }
      try {
        let buffer = segment.unpack();
        buffers.push(buffer);
        i++;
        delete this.received[i];
      } catch (e) {
        break;
      }
    }
    if (!buffers.length) {
      return;
    }
    this.confirmedMaxSegmentId = i;
    this.receivedLength = this.receivedMaxSegmentId - this.confirmedMaxSegmentId;
    let data;
    if (buffers.length === 1) {
      data = buffers[0];
    } else {
      data = Buffer.concat(buffers);
    }
    this.bytesRead += data.length;
    setImmediate(() => {
      this.emit('data', data);
    });
    if (!this.confirmTimer) {
      this.confirmTimer = setTimeout(() => {
        //发送确认包
        this.confirmTimer = 0;
        this._confirm();
      }, 5);
    }
  }

  _confirm() {
    //发送确认包
    let buffer = new Buffer(8);
    buffer.writeUInt8(CMD.CONFIRM, 0);
    this.idBuffer.copy(buffer, 1);
    buffer.writeUInt32LE(this.confirmedMaxSegmentId, 4);

    let buffers = [buffer];
    if (this.receivedMaxSegmentId > this.confirmedMaxSegmentId) {
      //有空白
      for (let offset = 1; offset < 200 && this.confirmedMaxSegmentId + offset <= this.receivedMaxSegmentId; offset++) {
        let i = this.confirmedMaxSegmentId + offset;
        let segment = this.received[i];
        if (segment && segment.count >= 3) {
          continue;
        }
        let sack = new Buffer(2);
        sack.writeUInt8(offset);
        let code = 1;
        if (!segment) {
          code = CMD.DATA_RESEND_ALL;
        } else {
          if (!segment.a) {
            code *= CMD.DATA_A;
          }
          if (!segment.b) {
            code *= CMD.DATA_B;
          }
          if (!segment.c) {
            code *= CMD.DATA_C;
          }
          if (!segment.p) {
            code *= CMD.DATA_RESEND_P;
          }
        }
        sack.writeUInt8(code, 1);
        buffers.push(sack);
      }
    }

    if (buffers.length > 1) {
      buffer = Buffer.concat(buffers);
    }
    let packet = new Packet(buffer);
    this.connection.send(packet);
  }
}

Stream.maxId = 16777216;

module.exports = Stream;
<commit_msg>adjust<commit_after>/**
 * @copyright Maichong Software Ltd. 2016 http://maichong.it
 * @date 2016-04-10
 * @author Liang <liang@maichong.it>
 */

'use strict';

const EventEmitter = require('events');
const Segment = require('./segment');
const Packet = require('./packet');
const CMD = require('./cmd');

const CONFIRM = 10;

const LBOUND = 50; //RTO 最小值
const UBOUND = 500;  //RTO 最大值

class Stream extends EventEmitter {
  constructor(id, connection) {
    super();
    this.id = id;
    this.connection = connection;
    this.sentMaxSegmentId = 0; //发送方,发送的最大Segment ID
    this.successMaxSegmentId = 0; //发送方,确认发送成功的最大Segment ID
    this.receivedMaxSegmentId = 0; //接收方,已经收到的最大Segment ID
    this.confirmedMaxSegmentId = 0; //接收方,已经确认的最大Segment ID
    this.received = {}; // 接收的 Segment 缓冲
    this.receivedLength = 0;
    this.confirmTimer = 0;//确认Timer
    this.lastAlive = Date.now();
    this.sent = {};//发送端,已经发送,待确认的Segment
    this.sentLength = 0;
    this.bytesRead = 0;
    this.bytesWritten = 0;
    this.remoteAddress = connection.remoteAddress;
    this.remotePort = connection.remotePort;
    this._writeQueue = [];
    this.writeTimer = 0;
    this.resentPacketCount = 0;
    this.resentPacketCountAuto = 0;
    this.resentPacketCountConfirm = 0;
    this._resend = {}; //重新发送列表
    this.closed = false;
    this.srtt = 100;

    this.aliveTimer = setInterval(() => {
      let now = Date.now();
      if (now - this.lastAlive > 300000) {
        //5分钟不活跃
        this.close();
        return;
      }
      if (this.receivedLength && now - this.lastAlive > 5000) {
        //5秒内不活跃,并且有需要重发的包
        //请求重发
        this._confirm();
        return;
      }
    }, 5000);

    this.resendTimer = setInterval(() => {
      let now = Date.now();
      let rto = this.connection.rto * 10;
      for (let i in this.sent) {
        let segment = this.sent[i];
        //TODO RTT
        if (now - segment.lastSent > rto) {
          if (this._resend[segment.id]) {
            continue;
          }
          segment.lastSent = now;
          segment.sack = 0;
          this._resend[segment.id] = segment;
          this.resentPacketCountAuto++;
        }
      }
      if (!this.confirmTimer && this.receivedLength) {
        this.confirmTimer = setTimeout(() => {
          this.confirmTimer = 0;
          this._confirm();
        }, 1);
      }

      for (let id in this._resend) {
        let segment = this._resend[id];
        delete this._resend[id];
        if (segment.a && !segment.a.onBus) {
          this.connection.send(segment.a);
          this.resentPacketCount++;
        }
        if (segment.b && !segment.b.onBus) {
          this.connection.send(segment.b);
          this.resentPacketCount++;
        }
        if (segment.c && !segment.c.onBus) {
          this.connection.send(segment.c);
          this.resentPacketCount++;
        }
        if (segment.p && !segment.p.onBus) {
          this.connection.send(segment.p);
          this.resentPacketCount++;
        }
        segment.lastSent = now;
        segment.sack = 0;
      }
    }, 10);
  }

  close() {
    //console.log('stream', this.id, 'close');
    if (this.connection) {
      this.emit('close');
      let buffer = new Buffer(4);
      buffer.writeUInt8(CMD.CLOSE_STREAM);
      this.idBuffer.copy(buffer, 1);
      let packet = new Packet(buffer);
      this.connection._send(packet);
    }
    this.closed = true;
    delete this._idBuffer;
    delete this.received;
    delete this.sent;
    delete this.connection;
    delete this._writeQueue;
    if (this.confirmTimer) {
      clearTimeout(this.confirmTimer);
    }
    clearInterval(this.aliveTimer);
    clearInterval(this.resendTimer);
  }

  get idBuffer() {
    if (!this._idBuffer) {
      this._idBuffer = Stream.idToBuffer(this.id);
    }
    return this._idBuffer;
  }

  static idToBuffer(id) {
    let tmp = new Buffer(4);
    let buffer = new Buffer(3);
    tmp.writeInt32LE(id, 0);
    tmp.copy(buffer, 0, 0, 3);
    return buffer;
  }

  static bufferToId(buffer) {
    let tmp = new Buffer(4);
    tmp.fill(0, 3);
    buffer.copy(tmp);
    return tmp.readInt32LE(0);
  }

  /**
   * @param data
   * @param [encoding]
   * @param [callback]
   */
  write(data, encoding, callback) {
    if (!data || !data.length) {
      return;
    }
    if (typeof data === 'string') {
      data = new Buffer(data, encoding);
    }

    //如果一次写入的数据大于单个包最大数据
    //拆开写入多次
    if (data.length > 4350) {
      for (let start = 0; start < data.length; start += 4350) {
        let end = start + 4350;
        if (end > data.length) {
          end = data.length;
        }
        this._writeQueue.push(data.slice(start, end));
      }
    } else {
      this._writeQueue.push(data);
    }
    if (!this.writeTimer) {
      this._write();
    }
  }

  _write() {
    if (this.closed) {
      return;
    }
    this.writeTimer = 0;
    if (!this._writeQueue.length) {
      return;
    }
    let rto = this.connection.rto;
    if (this.sentLength > rto * 5) {
      this.writeTimer = setTimeout(() => this._write(), 2);
      return;
    }
    let data = this._writeQueue.shift();
    if (!data) {
      return;
    }
    this.bytesWritten += data.length;
    this.sentMaxSegmentId++;
    let segment = new Segment(this.sentMaxSegmentId, this, data);
    segment.pack();
    segment.lastSent = Date.now();
    this.connection.send(segment.a);
    this.connection.send(segment.b);
    this.connection.send(segment.c);
    //this.connection.send(segment.p);
    this.sent[segment.id] = segment;
    this.sentLength++;
    if (this.sentLength < rto * 2) {
      this._write();
    } else {
      this.writeTimer = setTimeout(() => this._write(), parseInt(rto / 100));
    }
  }

  /**
   * 当收到Packet
   * @param packet
   */
  receive(packet) {
    this.lastAlive = Date.now();

    if (packet.cmd === CMD.CONFIRM) {
      this._receiveSACK(packet);
    }
  }

  _receiveSACK(packet) {
    const now = Date.now();
    let segmentId = packet.segmentId;
    for (let i = this.successMaxSegmentId + 1; i <= packet.segmentId; i++) {
      if (this.sent[i]) {
        let time = now - this.sent[i].createdAt;
        this.srtt = 0.9 * this.srtt + 0.1 * time;
        //this.srtt = Math.min(UBOUND, Math.max(LBOUND, this.srtt));
        this.sentLength--;
        delete this.sent[i];
        delete this._resend[i];
        //console.log('receive.remove', i);
      }
    }
    //console.log('confirm', segmentId - this.successMaxSegmentId);
    this.successMaxSegmentId = segmentId;

    //SACK
    let buffer = packet.buffer;
    let index = 8;
    let lastSegmentId = segmentId;
    while (buffer.length > index) {
      let offset = buffer.readUInt8(index);
      if (!offset) {
        throw new Error('offset ' + offset);
      }
      let code = buffer.readUInt8(index + 1);
      index += 2;
      let id = segmentId + offset;
      let segment = this.sent[id];
      if (!segment) {
        console.error('error , can not resend ', id);
        this.close();
        return;
      }
      while (id - lastSegmentId > 1) {
        lastSegmentId++;
        //中间有一些已经接收到了
        if (this.sent[lastSegmentId]) {
          let time = now - this.sent[lastSegmentId].createdAt;
          this.srtt = 0.9 * this.srtt + 0.1 * time;
          //this.srtt = Math.min(UBOUND, Math.max(LBOUND, this.srtt));
          this.sentLength--;
          delete this.sent[lastSegmentId];
          //console.log('remove', lastSegmentId);
        }
        //console.log(lastSegmentId, id);
      }
      lastSegmentId = id;

      if (segment.a && code % CMD.DATA_A !== 0) {
        delete segment.a;
        segment.count--;
      }

      if (segment.b && code % CMD.DATA_B !== 0) {
        delete segment.b;
        segment.count--;
      }

      if (segment.c && code % CMD.DATA_B !== 0) {
        delete segment.c;
        segment.count--;
      }

      if (segment.p && code % CMD.DATA_RESEND_P !== 0) {
        delete segment.p;
        segment.count--;
      }

      if (this._resend[segment.id]) {
        continue;
      }
      if (now - segment.lastSack < this.connection.rto) {
        continue;
      }
      if (segment.sack < CONFIRM - 1) {
        segment.lastSack = now;
        segment.sack++;
        continue;
      }
      this._resend[segment.id] = segment;
      segment.lastSent = now;
      segment.lastSack = now;
      segment.sack = 0;
      this.resentPacketCountConfirm++;
    }
  }

  /**
   * 当收到数据Packet
   * @param packet
   */
  receiveData(packet) {
    this.lastAlive = Date.now();
    let segmentId = packet.segmentId;
    if (segmentId < this.confirmedMaxSegmentId) {
      //已经确认,重复接收
      return;
    }

    if (segmentId - this.confirmedMaxSegmentId > 10000) {
      //segmentId太大,不接受
      return;
    }

    let segment = this.received[segmentId];
    if (!segment) {
      segment = this.received[segmentId] = new Segment(segmentId, this, null);
    }

    let cmd = packet.cmd;
    if (!segment.a && cmd % CMD.DATA_A === 0) {
      //a
      segment.a = packet;
      segment.count++;
    } else if (!segment.b && cmd % CMD.DATA_B === 0) {
      //b
      segment.b = packet;
      segment.count++;
    } else if (!segment.c && cmd % CMD.DATA_C === 0) {
      //c
      segment.c = packet;
      segment.count++;
    } else if (!segment.p && cmd % CMD.DATA_P === 0) {
      //p
      segment.p = packet;
      segment.count++;
    } else {
      //没有能够识别包,或重复接收
      if (!segment.count) {
        delete this.received[segmentId];
      }
      return;
    }

    if (segmentId > this.receivedMaxSegmentId) {
      this.receivedMaxSegmentId = segmentId;
    }
    this.receivedLength = this.receivedMaxSegmentId - this.confirmedMaxSegmentId;

    if (segment.count >= 3) {
      setImmediate(() => this._readData());
    }
  }

  _readData() {
    let i = this.confirmedMaxSegmentId;
    let buffers = [];
    while (true) {
      let segment = this.received[i + 1];
      if (!segment || segment.count < 3) {
        break;
      }
      try {
        let buffer = segment.unpack();
        buffers.push(buffer);
        i++;
        delete this.received[i];
      } catch (e) {
        break;
      }
    }
    if (!buffers.length) {
      return;
    }
    this.confirmedMaxSegmentId = i;
    this.receivedLength = this.receivedMaxSegmentId - this.confirmedMaxSegmentId;
    let data;
    if (buffers.length === 1) {
      data = buffers[0];
    } else {
      data = Buffer.concat(buffers);
    }
    this.bytesRead += data.length;
    setImmediate(() => {
      this.emit('data', data);
    });
    if (!this.confirmTimer) {
      this.confirmTimer = setTimeout(() => {
        //发送确认包
        this.confirmTimer = 0;
        this._confirm();
      }, 5);
    }
  }

  _confirm() {
    //发送确认包
    let buffer = new Buffer(8);
    buffer.writeUInt8(CMD.CONFIRM, 0);
    this.idBuffer.copy(buffer, 1);
    buffer.writeUInt32LE(this.confirmedMaxSegmentId, 4);

    let buffers = [buffer];
    if (this.receivedMaxSegmentId > this.confirmedMaxSegmentId) {
      //有空白
      for (let offset = 1; offset < 200 && this.confirmedMaxSegmentId + offset <= this.receivedMaxSegmentId; offset++) {
        let i = this.confirmedMaxSegmentId + offset;
        let segment = this.received[i];
        if (segment && segment.count >= 3) {
          continue;
        }
        let sack = new Buffer(2);
        sack.writeUInt8(offset);
        let code = 1;
        if (!segment) {
          code = CMD.DATA_RESEND_ALL;
        } else {
          if (!segment.a) {
            code *= CMD.DATA_A;
          }
          if (!segment.b) {
            code *= CMD.DATA_B;
          }
          if (!segment.c) {
            code *= CMD.DATA_C;
          }
          if (!segment.p) {
            code *= CMD.DATA_RESEND_P;
          }
        }
        sack.writeUInt8(code, 1);
        buffers.push(sack);
      }
    }

    if (buffers.length > 1) {
      buffer = Buffer.concat(buffers);
    }
    let packet = new Packet(buffer);
    this.connection.send(packet);
  }
}

Stream.maxId = 16777216;

module.exports = Stream;
<|endoftext|>


<commit_before>'use strict';

Stripe.DEFAULT_HOST = 'api.stripe.com';
Stripe.DEFAULT_PORT = '443';
Stripe.DEFAULT_BASE_PATH = '/v1/';
Stripe.DEFAULT_API_VERSION = null;

// Use node's default timeout:
Stripe.DEFAULT_TIMEOUT = require('http').createServer().timeout;

Stripe.PACKAGE_VERSION = require('../package.json').version;

Stripe.USER_AGENT = {
  bindings_version: Stripe.PACKAGE_VERSION,
  lang: 'node',
  lang_version: process.version,
  platform: process.platform,
  publisher: 'stripe',
  uname: null
};

Stripe.USER_AGENT_SERIALIZED = null;

var exec = require('child_process').exec;

var resources = {

  Account: require('./resources/Account'),
  Balance: require('./resources/Balance'),
  Charges: require('./resources/Charges'),
  Coupons: require('./resources/Coupons'),
  Customers: require('./resources/Customers'),
  Events: require('./resources/Events'),
  Invoices: require('./resources/Invoices'),
  InvoiceItems: require('./resources/InvoiceItems'),
  Plans: require('./resources/Plans'),
  Recipients: require('./resources/Recipients'),
  Tokens: require('./resources/Tokens'),
  Transfers: require('./resources/Transfers'),
  ApplicationFees: require('./resources/ApplicationFees'),

  // The following rely on pre-filled customer IDs:
  CustomerCards: require('./resources/CustomerCards'),
  CustomerSubscriptions: require('./resources/CustomerSubscriptions')

};

Stripe.StripeResource = require('./StripeResource');
Stripe.resources = resources;

function Stripe(key, version) {

  if (!(this instanceof Stripe)) {
    return new Stripe(key, version);
  }

  this._api = {
    auth: null,
    host: Stripe.DEFAULT_HOST,
    port: Stripe.DEFAULT_PORT,
    basePath: Stripe.DEFAULT_BASE_PATH,
    version: Stripe.DEFAULT_API_VERSION,
    timeout: Stripe.DEFAULT_TIMEOUT,
    dev: false
  };

  this._prepResources();
  this.setApiKey(key);
  this.setApiVersion(version);
}

Stripe.prototype = {

  setHost: function(host, port, protocol) {
    this._setApiField('host', host);
    if (port) this.setPort(port);
    if (protocol) this.setProtocol(protocol);
  },

  setProtocol: function(protocol) {
    this._setApiField('protocol', protocol.toLowerCase());
  },

  setPort: function(port) {
    this._setApiField('port', port);
  },

  setApiVersion: function(version) {
    if (version) {
      this._setApiField('version', version);
    }
  },

  setApiKey: function(key) {
    if (key) {
      this._setApiField(
        'auth',
        'Basic ' + new Buffer(key + ':').toString('base64')
      );
    }
  },

  setTimeout: function(timeout) {
    this._setApiField(
      'timeout',
      timeout == null ? Stripe.DEFAULT_TIMEOUT : timeout
    );
  },

  _setApiField: function(key, value) {
    this._api[key] = value;
  },

  getApiField: function(key) {
    return this._api[key];
  },

  getConstant: function(c) {
    return Stripe[c];
  },

  getClientUserAgent: function(cb) {
    if (Stripe.USER_AGENT_SERIALIZED) {
      return cb(Stripe.USER_AGENT_SERIALIZED);
    }
    exec('uname -a', function(err, uname) {
      Stripe.USER_AGENT.uname = uname || 'UNKNOWN';
      Stripe.USER_AGENT_SERIALIZED = JSON.stringify(Stripe.USER_AGENT);
      cb(Stripe.USER_AGENT_SERIALIZED);
    });
  },

  _prepResources: function() {

    for (var name in resources) {
      this[
        name[0].toLowerCase() + name.substring(1)
      ] = new resources[name](this);
    }

  }

};

Stripe.initialize = function(key, version){
  Stripe.prototype._prepResources = function() {
    for (var name in Stripe.resources) {
      console.log(name);
      Stripe[ name
        //name[0].toLowerCase() + name.substring(1)
      ] = new resources[name](this);
    }
    console.log(this);
  }
  var stripe = new Stripe(key);
  return stripe;
}

module.exports = Stripe;<commit_msg>Removes unused logs<commit_after>'use strict';

Stripe.DEFAULT_HOST = 'api.stripe.com';
Stripe.DEFAULT_PORT = '443';
Stripe.DEFAULT_BASE_PATH = '/v1/';
Stripe.DEFAULT_API_VERSION = null;

// Use node's default timeout:
Stripe.DEFAULT_TIMEOUT = require('http').createServer().timeout;

Stripe.PACKAGE_VERSION = require('../package.json').version;

Stripe.USER_AGENT = {
  bindings_version: Stripe.PACKAGE_VERSION,
  lang: 'node',
  lang_version: process.version,
  platform: process.platform,
  publisher: 'stripe',
  uname: null
};

Stripe.USER_AGENT_SERIALIZED = null;

var exec = require('child_process').exec;

var resources = {

  Account: require('./resources/Account'),
  Balance: require('./resources/Balance'),
  Charges: require('./resources/Charges'),
  Coupons: require('./resources/Coupons'),
  Customers: require('./resources/Customers'),
  Events: require('./resources/Events'),
  Invoices: require('./resources/Invoices'),
  InvoiceItems: require('./resources/InvoiceItems'),
  Plans: require('./resources/Plans'),
  Recipients: require('./resources/Recipients'),
  Tokens: require('./resources/Tokens'),
  Transfers: require('./resources/Transfers'),
  ApplicationFees: require('./resources/ApplicationFees'),

  // The following rely on pre-filled customer IDs:
  CustomerCards: require('./resources/CustomerCards'),
  CustomerSubscriptions: require('./resources/CustomerSubscriptions')

};

Stripe.StripeResource = require('./StripeResource');
Stripe.resources = resources;

function Stripe(key, version) {

  if (!(this instanceof Stripe)) {
    return new Stripe(key, version);
  }

  this._api = {
    auth: null,
    host: Stripe.DEFAULT_HOST,
    port: Stripe.DEFAULT_PORT,
    basePath: Stripe.DEFAULT_BASE_PATH,
    version: Stripe.DEFAULT_API_VERSION,
    timeout: Stripe.DEFAULT_TIMEOUT,
    dev: false
  };

  this._prepResources();
  this.setApiKey(key);
  this.setApiVersion(version);
}

Stripe.prototype = {

  setHost: function(host, port, protocol) {
    this._setApiField('host', host);
    if (port) this.setPort(port);
    if (protocol) this.setProtocol(protocol);
  },

  setProtocol: function(protocol) {
    this._setApiField('protocol', protocol.toLowerCase());
  },

  setPort: function(port) {
    this._setApiField('port', port);
  },

  setApiVersion: function(version) {
    if (version) {
      this._setApiField('version', version);
    }
  },

  setApiKey: function(key) {
    if (key) {
      this._setApiField(
        'auth',
        'Basic ' + new Buffer(key + ':').toString('base64')
      );
    }
  },

  setTimeout: function(timeout) {
    this._setApiField(
      'timeout',
      timeout == null ? Stripe.DEFAULT_TIMEOUT : timeout
    );
  },

  _setApiField: function(key, value) {
    this._api[key] = value;
  },

  getApiField: function(key) {
    return this._api[key];
  },

  getConstant: function(c) {
    return Stripe[c];
  },

  getClientUserAgent: function(cb) {
    if (Stripe.USER_AGENT_SERIALIZED) {
      return cb(Stripe.USER_AGENT_SERIALIZED);
    }
    exec('uname -a', function(err, uname) {
      Stripe.USER_AGENT.uname = uname || 'UNKNOWN';
      Stripe.USER_AGENT_SERIALIZED = JSON.stringify(Stripe.USER_AGENT);
      cb(Stripe.USER_AGENT_SERIALIZED);
    });
  },

  _prepResources: function() {

    for (var name in resources) {
      this[
        name[0].toLowerCase() + name.substring(1)
      ] = new resources[name](this);
    }

  }

};

Stripe.initialize = function(key, version){
  Stripe.prototype._prepResources = function() {
    for (var name in Stripe.resources) {
      Stripe[ name ] = new resources[name](this);
    }
  }
  var stripe = new Stripe(key);
  return stripe;
}

module.exports = Stripe;<|endoftext|>


<commit_before>/**
 * Structure
 * =========
 *
 * A convenient structure to extend objects from that comes with very common
 * boiler plate instance methods:
 * - fromObject
 * - fromBr
 * - toBw
 * - fromBuffer
 * - fromFastBuffer
 * - toBuffer
 * - toFastBuffer
 * - fromHex
 * - toHex
 * - fromString
 * - toString
 * - fromJSON
 * - toJSON
 *
 * As well as static methods for:
 * - fromObject
 * - fromBr
 * - fromBuffer
 * - fromFastBuffer
 * - fromHex
 * - fromString
 * - fromJSON
 *
 * The "expect" method also facilitates deserializing a sequence of buffers
 * into an object.
 */
'use strict'
let dependencies = {
  Br: require('./br'),
  Bw: require('./bw')
}

let inject = function (deps) {
  let Br = deps.Br
  let Bw = deps.Bw

  class Struct {
    constructor (obj) {
      this.fromObject(obj)
    }

    fromObject (obj) {
      if (!obj) {
        return this
      }
      for (let key of Object.keys(obj)) {
        if (obj[key] !== undefined) {
          this[key] = obj[key]
        }
      }
      return this
    }

    static fromObject (obj) {
      return new this().fromObject(obj)
    }

    fromBr (br) {
      if (!(br instanceof Br)) {
        throw new Error('br must be a buffer reader')
      }
      throw new Error('not implemented')
    }

    static fromBr (br) {
      return new this().fromBr(br)
    }

    asyncFromBr (br) {
      if (!(br instanceof Br)) {
        throw new Error('br must be a buffer reader')
      }
      throw new Error('not implemented')
    }

    static asyncFromBr (br) {
      return new this().asyncFromBr(br)
    }

    toBw (bw) {
      throw new Error('not implemented')
    }

    asyncToBw (bw) {
      throw new Error('not implemented')
    }

    /**
     * It is very often the case that you want to create a bitcoin object from a
     * stream of small buffers rather than from a buffer of the correct length.
     * For instance, if streaming from the network or disk. The genFromBuffers
     * method is a generator which produces an iterator. Use .next(buf) to pass
     * in a small buffer. The iterator will end when it has received enough data
     * to produce the object. In some cases it is able to yield the number of
     * bytes it is expecting, but that is not always known.
     */
    * genFromBuffers () {
      throw new Error('not implemented')
    }

    /**
     * A convenience method used by from the genFromBuffers* generators.
     * Basically lets you expect a certain number of bytes (len) and keeps
     * yielding until you give it enough. It yields the expected amount
     * remainIng, and returns an object containIng a buffer of the expected
     * length, and, if any, the remainder buffer.
     */
    * expect (len, startbuf) {
      let buf = startbuf
      let bw = new Bw()
      let gotlen = 0
      if (startbuf) {
        bw.write(startbuf)
        gotlen += startbuf.length
      }
      let remainderbuf
      while (gotlen < len) {
        let remainderlen = (len - gotlen)
        buf = yield remainderlen
        if (!buf) {
          continue
        }
        bw.write(buf)
        gotlen += buf.length
      }
      buf = bw.toBuffer()
      let overlen = gotlen - len
      remainderbuf = buf.slice(buf.length - overlen, buf.length)
      buf = buf.slice(0, buf.length - overlen)
      return {
        buf: buf,
        remainderbuf: remainderbuf
      }
    }

    /**
     * Convert a buffer into an object, i.e. deserialize the object.
     */
    fromBuffer (buf, ...rest) {
      if (!Buffer.isBuffer(buf)) {
        throw new Error('buf must be a buffer')
      }
      let br = new Br(buf)
      return this.fromBr(br, ...rest)
    }

    static fromBuffer (...rest) {
      return new this().fromBuffer(...rest)
    }

    asyncFromBuffer (buf, ...rest) {
      if (!Buffer.isBuffer(buf)) {
        throw new Error('buf must be a buffer')
      }
      let br = new Br(buf)
      return this.asyncFromBr(br, ...rest)
    }

    static asyncFromBuffer (buf, ...rest) {
      return new this().asyncFromBuffer(buf, ...rest)
    }

    /**
     * The complement of toFastBuffer - see description for toFastBuffer
     */
    fromFastBuffer (buf, ...rest) {
      if (buf.length === 0) {
        return this
      } else {
        return this.fromBuffer(buf, ...rest)
      }
    }

    static fromFastBuffer (...rest) {
      return new this().fromFastBuffer(...rest)
    }

    /**
     * Convert the object into a buffer, i.e. serialize the object. This method
     * may block the main thread.
     */
    toBuffer (...rest) {
      return this.toBw(...rest).toBuffer()
    }

    asyncToBuffer (...rest) {
      return this.asyncToBw(...rest).then((bw) => bw.toBuffer())
    }

    /**
     * Sometimes the toBuffer method has cryptography and blocks the main thread,
     * and we need a non-blocking way to serialize an object. That is what
     * toFastBuffer is. Of course it defaults to just using toBuffer if an object
     * hasn't implemented it. If your regular toBuffer method blocks, like with
     * Bip32, then you should implement this method to be non-blocking. This
     * method is used to send objects to the workers. i.e., for converting a
     * Bip32 object to a string, we need to encode it as a buffer in a
     * non-blocking manner with toFastBuffer, send it to a worker, then the
     * worker converts it to a string, which is a blocking operation.
     *
     * It is very common to want to convert a blank object to a zero length
     * buffer, so we can transport a blank object to a worker. So that behavior
     * is included by default.
     */
    toFastBuffer (...rest) {
      if (Object.keys(this).length === 0) {
        return new Buffer(0)
      } else {
        return this.toBuffer(...rest)
      }
    }

    fromHex (hex, ...rest) {
      let buf
      try {
        buf = new Buffer(hex, 'hex')
      } catch (e) {
        throw new Error('invalid hex string')
      }
      return this.fromBuffer(buf, ...rest)
    }

    static fromHex (hex, ...rest) {
      return new this().fromHex(hex, ...rest)
    }

    asyncFromHex (hex, ...rest) {
      let buf
      try {
        buf = new Buffer(hex, 'hex')
      } catch (e) {
        throw new Error('invalid hex string')
      }
      return this.asyncFromBuffer(buf, ...rest)
    }

    static asyncFromHex (hex, ...rest) {
      return new this().asyncFromHex(hex, ...rest)
    }

    fromFastHex (hex, ...rest) {
      let buf
      try {
        buf = new Buffer(hex, 'hex')
      } catch (e) {
        throw new Error('invalid hex string')
      }
      return this.fromFastBuffer(buf, ...rest)
    }

    static fromFastHex (hex, ...rest) {
      return new this().fromFastHex(hex, ...rest)
    }

    toHex (...rest) {
      return this.toBuffer(...rest).toString('hex')
    }

    asyncToHex (...rest) {
      return this.asyncToBuffer(...rest).then((buf) => buf.toString('hex'))
    }

    toFastHex (...rest) {
      return this.toFastBuffer(...rest).toString('hex')
    }

    fromString (str, ...rest) {
      if (typeof str !== 'string') {
        throw new Error('str must be a string')
      }
      return this.fromHex(str, ...rest)
    }

    static fromString (str, ...rest) {
      return new this().fromString(str, ...rest)
    }

    asyncFromString (str, ...rest) {
      if (typeof str !== 'string') {
        throw new Error('str must be a string')
      }
      return this.asyncFromHex(str, ...rest)
    }

    static asyncFromString (str, ...rest) {
      return new this().asyncFromString(str, ...rest)
    }

    toString (...rest) {
      return this.toHex(...rest)
    }

    asyncToString (...rest) {
      return this.asyncToHex(...rest)
    }

    fromJSON (json) {
      throw new Error('not implemented')
    }

    static fromJSON (json, ...rest) {
      return new this().fromJSON(json, ...rest)
    }

    asyncFromJSON (json, ...rest) {
      throw new Error('not implemented')
    }

    static asyncFromJSON (json, ...rest) {
      return new this().asyncFromJSON(json, ...rest)
    }

    toJSON () {
      var json = {}
      for (var val in this) {
        if (this[val] instanceof Array) {
          let arr = []
          for (var i in this[val]) {
            if (typeof this[val][i].toJSON === 'function') {
              arr.push(this[val][i].toJSON())
            } else {
              arr.push(JSON.stringify(this[val][i]))
            }
          }
          json[val] = arr
        } else if (typeof this[val] === 'object' && typeof this[val].toJSON === 'function') {
          json[val] = this[val].toJSON()
        } else if (typeof this[val] !== 'undefined') {
          json[val] = JSON.stringify(this[val])
        }
      }
      return json
      // throw new Error('not implemented')
    }

    asyncToJSON () {
      throw new Error('not implemented')
    }
  }

  return Struct
}

inject = require('injecter')(inject, dependencies)
let Struct = inject()
module.exports = Struct
<commit_msg>added more case to toJSON() definition<commit_after>/**
 * Structure
 * =========
 *
 * A convenient structure to extend objects from that comes with very common
 * boiler plate instance methods:
 * - fromObject
 * - fromBr
 * - toBw
 * - fromBuffer
 * - fromFastBuffer
 * - toBuffer
 * - toFastBuffer
 * - fromHex
 * - toHex
 * - fromString
 * - toString
 * - fromJSON
 * - toJSON
 *
 * As well as static methods for:
 * - fromObject
 * - fromBr
 * - fromBuffer
 * - fromFastBuffer
 * - fromHex
 * - fromString
 * - fromJSON
 *
 * The "expect" method also facilitates deserializing a sequence of buffers
 * into an object.
 */
'use strict'
let dependencies = {
  Br: require('./br'),
  Bw: require('./bw')
}

let inject = function (deps) {
  let Br = deps.Br
  let Bw = deps.Bw

  class Struct {
    constructor (obj) {
      this.fromObject(obj)
    }

    fromObject (obj) {
      if (!obj) {
        return this
      }
      for (let key of Object.keys(obj)) {
        if (obj[key] !== undefined) {
          this[key] = obj[key]
        }
      }
      return this
    }

    static fromObject (obj) {
      return new this().fromObject(obj)
    }

    fromBr (br) {
      if (!(br instanceof Br)) {
        throw new Error('br must be a buffer reader')
      }
      throw new Error('not implemented')
    }

    static fromBr (br) {
      return new this().fromBr(br)
    }

    asyncFromBr (br) {
      if (!(br instanceof Br)) {
        throw new Error('br must be a buffer reader')
      }
      throw new Error('not implemented')
    }

    static asyncFromBr (br) {
      return new this().asyncFromBr(br)
    }

    toBw (bw) {
      throw new Error('not implemented')
    }

    asyncToBw (bw) {
      throw new Error('not implemented')
    }

    /**
     * It is very often the case that you want to create a bitcoin object from a
     * stream of small buffers rather than from a buffer of the correct length.
     * For instance, if streaming from the network or disk. The genFromBuffers
     * method is a generator which produces an iterator. Use .next(buf) to pass
     * in a small buffer. The iterator will end when it has received enough data
     * to produce the object. In some cases it is able to yield the number of
     * bytes it is expecting, but that is not always known.
     */
    * genFromBuffers () {
      throw new Error('not implemented')
    }

    /**
     * A convenience method used by from the genFromBuffers* generators.
     * Basically lets you expect a certain number of bytes (len) and keeps
     * yielding until you give it enough. It yields the expected amount
     * remainIng, and returns an object containIng a buffer of the expected
     * length, and, if any, the remainder buffer.
     */
    * expect (len, startbuf) {
      let buf = startbuf
      let bw = new Bw()
      let gotlen = 0
      if (startbuf) {
        bw.write(startbuf)
        gotlen += startbuf.length
      }
      let remainderbuf
      while (gotlen < len) {
        let remainderlen = (len - gotlen)
        buf = yield remainderlen
        if (!buf) {
          continue
        }
        bw.write(buf)
        gotlen += buf.length
      }
      buf = bw.toBuffer()
      let overlen = gotlen - len
      remainderbuf = buf.slice(buf.length - overlen, buf.length)
      buf = buf.slice(0, buf.length - overlen)
      return {
        buf: buf,
        remainderbuf: remainderbuf
      }
    }

    /**
     * Convert a buffer into an object, i.e. deserialize the object.
     */
    fromBuffer (buf, ...rest) {
      if (!Buffer.isBuffer(buf)) {
        throw new Error('buf must be a buffer')
      }
      let br = new Br(buf)
      return this.fromBr(br, ...rest)
    }

    static fromBuffer (...rest) {
      return new this().fromBuffer(...rest)
    }

    asyncFromBuffer (buf, ...rest) {
      if (!Buffer.isBuffer(buf)) {
        throw new Error('buf must be a buffer')
      }
      let br = new Br(buf)
      return this.asyncFromBr(br, ...rest)
    }

    static asyncFromBuffer (buf, ...rest) {
      return new this().asyncFromBuffer(buf, ...rest)
    }

    /**
     * The complement of toFastBuffer - see description for toFastBuffer
     */
    fromFastBuffer (buf, ...rest) {
      if (buf.length === 0) {
        return this
      } else {
        return this.fromBuffer(buf, ...rest)
      }
    }

    static fromFastBuffer (...rest) {
      return new this().fromFastBuffer(...rest)
    }

    /**
     * Convert the object into a buffer, i.e. serialize the object. This method
     * may block the main thread.
     */
    toBuffer (...rest) {
      return this.toBw(...rest).toBuffer()
    }

    asyncToBuffer (...rest) {
      return this.asyncToBw(...rest).then((bw) => bw.toBuffer())
    }

    /**
     * Sometimes the toBuffer method has cryptography and blocks the main thread,
     * and we need a non-blocking way to serialize an object. That is what
     * toFastBuffer is. Of course it defaults to just using toBuffer if an object
     * hasn't implemented it. If your regular toBuffer method blocks, like with
     * Bip32, then you should implement this method to be non-blocking. This
     * method is used to send objects to the workers. i.e., for converting a
     * Bip32 object to a string, we need to encode it as a buffer in a
     * non-blocking manner with toFastBuffer, send it to a worker, then the
     * worker converts it to a string, which is a blocking operation.
     *
     * It is very common to want to convert a blank object to a zero length
     * buffer, so we can transport a blank object to a worker. So that behavior
     * is included by default.
     */
    toFastBuffer (...rest) {
      if (Object.keys(this).length === 0) {
        return new Buffer(0)
      } else {
        return this.toBuffer(...rest)
      }
    }

    fromHex (hex, ...rest) {
      let buf
      try {
        buf = new Buffer(hex, 'hex')
      } catch (e) {
        throw new Error('invalid hex string')
      }
      return this.fromBuffer(buf, ...rest)
    }

    static fromHex (hex, ...rest) {
      return new this().fromHex(hex, ...rest)
    }

    asyncFromHex (hex, ...rest) {
      let buf
      try {
        buf = new Buffer(hex, 'hex')
      } catch (e) {
        throw new Error('invalid hex string')
      }
      return this.asyncFromBuffer(buf, ...rest)
    }

    static asyncFromHex (hex, ...rest) {
      return new this().asyncFromHex(hex, ...rest)
    }

    fromFastHex (hex, ...rest) {
      let buf
      try {
        buf = new Buffer(hex, 'hex')
      } catch (e) {
        throw new Error('invalid hex string')
      }
      return this.fromFastBuffer(buf, ...rest)
    }

    static fromFastHex (hex, ...rest) {
      return new this().fromFastHex(hex, ...rest)
    }

    toHex (...rest) {
      return this.toBuffer(...rest).toString('hex')
    }

    asyncToHex (...rest) {
      return this.asyncToBuffer(...rest).then((buf) => buf.toString('hex'))
    }

    toFastHex (...rest) {
      return this.toFastBuffer(...rest).toString('hex')
    }

    fromString (str, ...rest) {
      if (typeof str !== 'string') {
        throw new Error('str must be a string')
      }
      return this.fromHex(str, ...rest)
    }

    static fromString (str, ...rest) {
      return new this().fromString(str, ...rest)
    }

    asyncFromString (str, ...rest) {
      if (typeof str !== 'string') {
        throw new Error('str must be a string')
      }
      return this.asyncFromHex(str, ...rest)
    }

    static asyncFromString (str, ...rest) {
      return new this().asyncFromString(str, ...rest)
    }

    toString (...rest) {
      return this.toHex(...rest)
    }

    asyncToString (...rest) {
      return this.asyncToHex(...rest)
    }

    fromJSON (json) {
      throw new Error('not implemented')
    }

    static fromJSON (json, ...rest) {
      return new this().fromJSON(json, ...rest)
    }

    asyncFromJSON (json, ...rest) {
      throw new Error('not implemented')
    }

    static asyncFromJSON (json, ...rest) {
      return new this().asyncFromJSON(json, ...rest)
    }

    toJSON () {
      var json = {}
      for (var val in this) {
        // arrays
        if (this[val] instanceof Array) {
          let arr = []
          for (var i in this[val]) {
            if (typeof this[val][i].toJSON === 'function') {
              arr.push(this[val][i].toJSON())
            } else {
              arr.push(JSON.stringify(this[val][i]))
            }
          }
          json[val] = arr
        // objects
        } else if (typeof this[val] === 'object' && typeof this[val].toJSON === 'function') {
          json[val] = this[val].toJSON()
        // booleans, numbers, and strings
        } else if (typeof this[val] === 'boolean' || typeof this[val] === 'number' || typeof this[val] === 'string') {
          json[val] = this[val]
        // throw an error for objects that do not implement toJSON
        } else if (typeof this[val] === 'object') {
          throw new Error('not implemented')
        }
      }
      return json
      // throw new Error('not implemented')
    }

    asyncToJSON () {
      throw new Error('not implemented')
    }
  }

  return Struct
}

inject = require('injecter')(inject, dependencies)
let Struct = inject()
module.exports = Struct
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';

export default class AdTextInput extends Component {
  static propTypes = {
    onSave: PropTypes.func.isRequired,
    name: PropTypes.string,
    placeholder: PropTypes.string,
    newAd: PropTypes.bool
  };

  constructor(props, context) {
    super(props, context);
    this.state = {
      name: this.props.name || ''
    };
  }

  handleSubmit(e) {
    const text = e.target.value.trim();
    if (e.which === 13) {
      this.props.onSave(text);
      if (this.props.newAd) {
        this.setState({ text: '' });
      }
    }
  }

  handleChange(e) {
    this.setState({ text: e.target.value });
  }

  handleBlur(e) {
    if (!this.props.newAd) {
      this.props.onSave(e.target.value);
    }
  }

  render() {
    return (
      <input className={classnames({
              'new-ad': this.props.newAd
             })}
             type='text'
             placeholder={this.props.placeholder}
             autoFocus='true'
             value={this.state.name}
             onBlur={::this.handleBlur}
             onChange={::this.handleChange}
             onKeyDown={::this.handleSubmit} />
    );
  }
}
<commit_msg>Delete AdTextInput.js<commit_after><|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import Checklist from 'parser/shared/modules/features/Checklist2';
import Rule from 'parser/shared/modules/features/Checklist2/Rule';
import Requirement from 'parser/shared/modules/features/Checklist2/Requirement';
import PreparationRule from 'parser/shared/modules/features/Checklist2/PreparationRule';
import GenericCastEfficiencyRequirement from 'parser/shared/modules/features/Checklist2/GenericCastEfficiencyRequirement';
import ResourceIcon from 'common/ResourceIcon';
import RESOURCE_TYPES from 'game/RESOURCE_TYPES';
import SpellIcon from 'common/SpellIcon';

class BeastMasteryChecklist extends React.PureComponent {
  static propTypes = {
    castEfficiency: PropTypes.object.isRequired,
    combatant: PropTypes.shape({
      hasTalent: PropTypes.func.isRequired,
      hasTrinket: PropTypes.func.isRequired,
    }).isRequired,
    thresholds: PropTypes.object.isRequired,
  };

  render() {
    const { combatant, castEfficiency, thresholds } = this.props;
    const AbilityRequirement = props => (
      <GenericCastEfficiencyRequirement
        castEfficiency={castEfficiency.getCastEfficiencyForSpellId(props.spell)}
        {...props}
      />
    );
    return (
      <Checklist>
        <Rule
          name="Use core abilities as often as possible"
          description={(
            <>
              Using your core abilities as often as possible can help raise your dps significantly. Some help more than others, but as a general rule of thumb you should be looking to use most of your damaging abilities and damage cooldowns as often as possible, unless you need to save them for a prioirty burst phase that is coming up soon.
              {'  '}
              More info.
            </>
          )}
        >
          {combatant.hasTalent(SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id) && <AbilityRequirement spell={SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id} />}
          {!combatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id) && <AbilityRequirement spell={SPELLS.FIRE_ELEMENTAL.id} />}
          {combatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id) && <AbilityRequirement spell={SPELLS.STORM_ELEMENTAL_TALENT.id} />}
          {combatant.hasTalent(SPELLS.ICEFURY_TALENT.id) && <AbilityRequirement spell={SPELLS.ICEFURY_TALENT.id} />}
          {combatant.hasTalent(SPELLS.STORMKEEPER_TALENT.id) && <AbilityRequirement spell={SPELLS.STORMKEEPER_TALENT.id} />}
          {combatant.hasTalent(SPELLS.LIQUID_MAGMA_TOTEM_TALENT.id) && <AbilityRequirement spell={SPELLS.LIQUID_MAGMA_TOTEM_TALENT.id} />}
          {combatant.hasTalent(SPELLS.ELEMENTAL_BLAST_TALENT.id) && <AbilityRequirement spell={SPELLS.ELEMENTAL_BLAST_TALENT.id} />}
        </Rule>

        <PreparationRule thresholds={thresholds} />
      </Checklist>
    );
  }
}

export default BeastMasteryChecklist;
<commit_msg>Removing unused imports<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import SPELLS from 'common/SPELLS';
import Checklist from 'parser/shared/modules/features/Checklist2';
import Rule from 'parser/shared/modules/features/Checklist2/Rule';
import PreparationRule from 'parser/shared/modules/features/Checklist2/PreparationRule';
import GenericCastEfficiencyRequirement from 'parser/shared/modules/features/Checklist2/GenericCastEfficiencyRequirement';

class BeastMasteryChecklist extends React.PureComponent {
  static propTypes = {
    castEfficiency: PropTypes.object.isRequired,
    combatant: PropTypes.shape({
      hasTalent: PropTypes.func.isRequired,
      hasTrinket: PropTypes.func.isRequired,
    }).isRequired,
    thresholds: PropTypes.object.isRequired,
  };

  render() {
    const { combatant, castEfficiency, thresholds } = this.props;
    const AbilityRequirement = props => (
      <GenericCastEfficiencyRequirement
        castEfficiency={castEfficiency.getCastEfficiencyForSpellId(props.spell)}
        {...props}
      />
    );
    return (
      <Checklist>
        <Rule
          name="Use core abilities as often as possible"
          description={(
            <>
              Using your core abilities as often as possible can help raise your dps significantly. Some help more than others, but as a general rule of thumb you should be looking to use most of your damaging abilities and damage cooldowns as often as possible, unless you need to save them for a prioirty burst phase that is coming up soon.
              {'  '}
              More info.
            </>
          )}
        >
          {combatant.hasTalent(SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id) && <AbilityRequirement spell={SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id} />}
          {!combatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id) && <AbilityRequirement spell={SPELLS.FIRE_ELEMENTAL.id} />}
          {combatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id) && <AbilityRequirement spell={SPELLS.STORM_ELEMENTAL_TALENT.id} />}
          {combatant.hasTalent(SPELLS.ICEFURY_TALENT.id) && <AbilityRequirement spell={SPELLS.ICEFURY_TALENT.id} />}
          {combatant.hasTalent(SPELLS.STORMKEEPER_TALENT.id) && <AbilityRequirement spell={SPELLS.STORMKEEPER_TALENT.id} />}
          {combatant.hasTalent(SPELLS.LIQUID_MAGMA_TOTEM_TALENT.id) && <AbilityRequirement spell={SPELLS.LIQUID_MAGMA_TOTEM_TALENT.id} />}
          {combatant.hasTalent(SPELLS.ELEMENTAL_BLAST_TALENT.id) && <AbilityRequirement spell={SPELLS.ELEMENTAL_BLAST_TALENT.id} />}
        </Rule>

        <PreparationRule thresholds={thresholds} />
      </Checklist>
    );
  }
}

export default BeastMasteryChecklist;
<|endoftext|>