Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add failure per seconds as a series in the chart #1140

Merged
merged 10 commits into from Nov 12, 2019
4 changes: 2 additions & 2 deletions locust/static/locust.js
Expand Up @@ -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", "FPS"], "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");

Expand All @@ -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_fps]);
responseTimeChart.addValue([report.current_response_time_percentile_50, report.current_response_time_percentile_95]);
usersChart.addValue([report.user_count]);
}
Expand Down
28 changes: 25 additions & 3 deletions locust/stats.py
Expand Up @@ -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
"""
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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_fps(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:
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -419,15 +438,16 @@ 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),
self.avg_response_time,
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_fps or 0
)

def get_response_time_percentile(self, percent):
Expand Down Expand Up @@ -639,11 +659,13 @@ 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("-" * (80 + STATS_NAME_WIDTH))
total_rps = 0
total_fps = 0
total_reqs = 0
total_failures = 0
for key in sorted(six.iterkeys(stats)):
r = stats[key]
total_rps += r.current_rps
total_fps += r.current_fps
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable doesn't seem to be used, so it should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to include it in the print_stats table. I'll do that instead.

total_reqs += r.num_requests
total_failures += r.num_failures
console_logger.info(r)
Expand Down
10 changes: 10 additions & 0 deletions locust/test/test_stats.py
Expand Up @@ -63,6 +63,16 @@ def test_current_rps(self):
self.stats.total.last_request_timestamp = int(time.time()) + 25
self.assertEqual(self.s.current_rps, 0)

def test_current_fps(self):
self.stats.total.last_request_timestamp = int(time.time()) + 4
self.assertEqual(self.s.current_fps, 1.5)

self.stats.total.last_request_timestamp = int(time.time()) + 12
self.assertEqual(self.s.current_fps, 0.3)

self.stats.total.last_request_timestamp = int(time.time()) + 25
self.assertEqual(self.s.current_fps, 0)

def test_num_reqs_fails(self):
self.assertEqual(self.s.num_requests, 9)
self.assertEqual(self.s.num_failures, 3)
Expand Down
1 change: 1 addition & 0 deletions locust/web.py
Expand Up @@ -134,6 +134,7 @@ def request_stats():
"min_response_time": 0 if s.min_response_time is None else proper_round(s.min_response_time),
"max_response_time": proper_round(s.max_response_time),
"current_rps": s.current_rps,
"current_fps": s.current_fps,
"median_response_time": s.median_response_time,
"ninetieth_response_time": s.get_response_time_percentile(0.9),
"avg_content_length": s.avg_content_length,
Expand Down