diff --git a/locust/static/locust.js b/locust/static/locust.js index 60dc6e6b1c..a2bac46281 100644 --- a/locust/static/locust.js +++ b/locust/static/locust.js @@ -137,7 +137,7 @@ $(".stats_label").click(function(event) { }); // init charts -var rpsChart = new LocustLineChart($(".charts-container"), "Total Requests per Second", ["RPS"], "reqs/s"); +var rpsChart = new LocustLineChart($(".charts-container"), "Total Requests per Second", ["RPS", "Failures/s"], "reqs/s"); var responseTimeChart = new LocustLineChart($(".charts-container"), "Response Times (ms)", ["Median Response Time", "95% percentile"], "ms"); var usersChart = new LocustLineChart($(".charts-container"), "Number of Users", ["Users"], "users"); @@ -159,7 +159,7 @@ function updateStats() { // get total stats row var total = report.stats[report.stats.length-1]; // update charts - rpsChart.addValue([total.current_rps]); + rpsChart.addValue([total.current_rps, total.current_fail_per_sec]); responseTimeChart.addValue([report.current_response_time_percentile_50, report.current_response_time_percentile_95]); usersChart.addValue([report.user_count]); } diff --git a/locust/stats.py b/locust/stats.py index 8b227c4a47..c3aa494cb3 100644 --- a/locust/stats.py +++ b/locust/stats.py @@ -178,6 +178,9 @@ class StatsEntry(object): num_reqs_per_sec = None """ A {second => request_count} dict that holds the number of requests made per second """ + + num_fail_per_sec = None + """ A (second => failure_count) dict that hold the number of failures per second """ response_times = None """ @@ -231,6 +234,7 @@ def reset(self): self.max_response_time = 0 self.last_request_timestamp = None self.num_reqs_per_sec = {} + self.num_fail_per_sec = {} self.total_content_length = 0 if self.use_response_times_cache: self.response_times_cache = OrderedDict() @@ -286,6 +290,8 @@ def _log_response_time(self, response_time): def log_error(self, error): self.num_failures += 1 + t = int(time.time()) + self.num_fail_per_sec[t] = self.num_fail_per_sec.setdefault(t, 0) + 1 @property def fail_ratio(self): @@ -330,6 +336,15 @@ def current_rps(self): reqs = [self.num_reqs_per_sec.get(t, 0) for t in range(slice_start_time, self.stats.last_request_timestamp-2)] return avg(reqs) + @property + def current_fail_per_sec(self): + if self.stats.last_request_timestamp is None: + return 0 + slice_start_time = max(self.stats.last_request_timestamp - 12, int(self.stats.start_time or 0)) + + reqs = [self.num_fail_per_sec.get(t, 0) for t in range(slice_start_time, self.stats.last_request_timestamp-2)] + return avg(reqs) + @property def total_rps(self): if not self.stats.last_request_timestamp or not self.stats.start_time: @@ -370,7 +385,9 @@ def extend(self, other): for key in other.response_times: self.response_times[key] = self.response_times.get(key, 0) + other.response_times[key] for key in other.num_reqs_per_sec: - self.num_reqs_per_sec[key] = self.num_reqs_per_sec.get(key, 0) + other.num_reqs_per_sec[key] + self.num_reqs_per_sec[key] = self.num_reqs_per_sec.get(key, 0) + other.num_reqs_per_sec[key] + for key in other.num_fail_per_sec: + self.num_fail_per_sec[key] = self.num_fail_per_sec.get(key, 0) + other.num_fail_per_sec[key] def serialize(self): return { @@ -387,6 +404,7 @@ def serialize(self): "total_content_length": self.total_content_length, "response_times": self.response_times, "num_reqs_per_sec": self.num_reqs_per_sec, + "num_fail_per_sec": self.num_fail_per_sec, } @classmethod @@ -404,6 +422,7 @@ def unserialize(cls, data): "total_content_length", "response_times", "num_reqs_per_sec", + "num_fail_per_sec", ]: setattr(obj, key, data[key]) return obj @@ -419,7 +438,7 @@ def get_stripped_report(self): def __str__(self): fail_percent = self.fail_ratio * 100 - return (" %-" + str(STATS_NAME_WIDTH) + "s %7d %12s %7d %7d %7d | %7d %7.2f") % ( + return (" %-" + str(STATS_NAME_WIDTH) + "s %7d %12s %7d %7d %7d | %7d %7.2f %7.2f") % ( (self.method and self.method + " " or "") + self.name, self.num_requests, "%d(%.2f%%)" % (self.num_failures, fail_percent), @@ -427,7 +446,8 @@ def __str__(self): self.min_response_time or 0, self.max_response_time, self.median_response_time or 0, - self.current_rps or 0 + self.current_rps or 0, + self.current_fail_per_sec or 0 ) def get_response_time_percentile(self, percent): @@ -636,14 +656,16 @@ def on_slave_report(client_id, data): def print_stats(stats): - console_logger.info((" %-" + str(STATS_NAME_WIDTH) + "s %7s %12s %7s %7s %7s | %7s %7s") % ('Name', '# reqs', '# fails', 'Avg', 'Min', 'Max', 'Median', 'req/s')) + console_logger.info((" %-" + str(STATS_NAME_WIDTH) + "s %7s %12s %7s %7s %7s | %7s %7s %7s") % ('Name', '# reqs', '# fails', 'Avg', 'Min', 'Max', 'Median', 'req/s', 'failures/s')) console_logger.info("-" * (80 + STATS_NAME_WIDTH)) total_rps = 0 + total_fail_per_sec = 0 total_reqs = 0 total_failures = 0 for key in sorted(six.iterkeys(stats)): r = stats[key] total_rps += r.current_rps + total_fail_per_sec += r.current_fail_per_sec total_reqs += r.num_requests total_failures += r.num_failures console_logger.info(r) @@ -654,7 +676,7 @@ def print_stats(stats): except ZeroDivisionError: fail_percent = 0 - console_logger.info((" %-" + str(STATS_NAME_WIDTH) + "s %7d %12s %42.2f") % ('Aggregated', total_reqs, "%d(%.2f%%)" % (total_failures, fail_percent), total_rps)) + console_logger.info((" %-" + str(STATS_NAME_WIDTH) + "s %7d %12s %42.2f %7.2f") % ('Aggregated', total_reqs, "%d(%.2f%%)" % (total_failures, fail_percent), total_rps, total_fail_per_sec)) console_logger.info("") def print_percentile_stats(stats): diff --git a/locust/templates/index.html b/locust/templates/index.html index e43e6cd20f..a19bfa68ff 100644 --- a/locust/templates/index.html +++ b/locust/templates/index.html @@ -121,6 +121,7 @@