From 7a2d9d723b203d0ec13bb8d4873507ab14f54cc1 Mon Sep 17 00:00:00 2001 From: Lars Holmberg Date: Sun, 22 Sep 2019 10:10:20 +0200 Subject: [PATCH 1/4] Allow None response time for requests --- locust/stats.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/locust/stats.py b/locust/stats.py index b49beaf3c9..7159d75709 100644 --- a/locust/stats.py +++ b/locust/stats.py @@ -52,6 +52,8 @@ def calculate_response_time_percentile(response_times, num_requests, percent): processed_count += response_times[response_time] if(num_requests - processed_count <= num_of_request): return response_time + # if all response times were None + return 0 def diff_response_time_dicts(latest, old): @@ -246,6 +248,8 @@ def _log_time_of_request(self, t): self.last_request_timestamp = t def _log_response_time(self, response_time): + if response_time is None: + return self.total_response_time += response_time From 1a2cb647b0363802e5b086f3896cd6a5a3d7879f Mon Sep 17 00:00:00 2001 From: Lars Holmberg Date: Mon, 21 Oct 2019 09:25:33 +0200 Subject: [PATCH 2/4] Add test for events with None response time (failing at the moment) --- locust/test/test_runners.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/locust/test/test_runners.py b/locust/test/test_runners.py index 14ad50f910..cd6ad33459 100644 --- a/locust/test/test_runners.py +++ b/locust/test/test_runners.py @@ -159,6 +159,30 @@ class MyTestLocust(Locust): s = master.stats.get("/", "GET") self.assertEqual(700, s.median_response_time) + def test_slave_stats_report_with_none_response_times(self): + class MyTestLocust(Locust): + pass + + with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: + master = MasterLocustRunner(MyTestLocust, self.options) + server.mocked_send(Message("client_ready", None, "fake_client")) + + master.stats.get("/", "GET").log(100, 23455) + master.stats.get("/", "GET").log(800, 23455) + master.stats.get("/", "GET").log(700, 23455) + master.stats.get("/", "GET").log(None, 23455) + master.stats.get("/other", "GET").log(None, 23455) + + data = {"user_count":1} + events.report_to_master.fire(client_id="fake_client", data=data) + master.stats.clear_all() + + server.mocked_send(Message("stats", data, "fake_client")) + s = master.stats.get("/", "GET") + self.assertEqual(700, s.median_response_time) + s = master.stats.get("/other", "GET") + self.assertEqual(0, s.median_response_time) + def test_master_marks_downed_slaves_as_missing(self): class MyTestLocust(Locust): pass From cc40ce2ec6befafa21a03a48dc3a4b3a57b43166 Mon Sep 17 00:00:00 2001 From: Lars Holmberg Date: Mon, 21 Oct 2019 11:10:53 +0200 Subject: [PATCH 3/4] Fix propagation of num_none_requests. Add test for median calculations and "only async". Add test for master total stats with none response times. --- locust/stats.py | 16 ++++++++-- locust/test/test_runners.py | 61 +++++++++++++++++++++++++++++++------ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/locust/stats.py b/locust/stats.py index 7159d75709..e27076c366 100644 --- a/locust/stats.py +++ b/locust/stats.py @@ -83,6 +83,10 @@ def __init__(self): def num_requests(self): return self.total.num_requests + @property + def num_none_requests(self): + return self.total.num_none_requests + @property def num_failures(self): return self.total.num_failures @@ -157,6 +161,9 @@ class StatsEntry(object): num_requests = None """ The number of requests made """ + num_none_requests = None + """ The number of requests made with a None response time (typically async requests) """ + num_failures = None """ Number of failed request """ @@ -216,6 +223,7 @@ def __init__(self, stats, name, method, use_response_times_cache=False): def reset(self): self.start_time = time.time() self.num_requests = 0 + self.num_none_requests = 0 self.num_failures = 0 self.total_response_time = 0 self.response_times = {} @@ -249,6 +257,7 @@ def _log_time_of_request(self, t): def _log_response_time(self, response_time): if response_time is None: + self.num_none_requests += 1 return self.total_response_time += response_time @@ -291,7 +300,7 @@ def fail_ratio(self): @property def avg_response_time(self): try: - return float(self.total_response_time) / self.num_requests + return float(self.total_response_time) / (self.num_requests - self.num_none_requests) except ZeroDivisionError: return 0 @@ -299,7 +308,7 @@ def avg_response_time(self): def median_response_time(self): if not self.response_times: return 0 - median = median_from_dict(self.num_requests, self.response_times) + median = median_from_dict(self.num_requests - self.num_none_requests, self.response_times) or 0 # Since we only use two digits of precision when calculating the median response time # while still using the exact values for min and max response times, the following checks @@ -344,6 +353,7 @@ def extend(self, other): self.start_time = min(self.start_time, other.start_time) self.num_requests = self.num_requests + other.num_requests + self.num_none_requests = self.num_none_requests + other.num_none_requests self.num_failures = self.num_failures + other.num_failures self.total_response_time = self.total_response_time + other.total_response_time self.max_response_time = max(self.max_response_time, other.max_response_time) @@ -366,6 +376,7 @@ def serialize(self): "last_request_timestamp": self.last_request_timestamp, "start_time": self.start_time, "num_requests": self.num_requests, + "num_none_requests": self.num_none_requests, "num_failures": self.num_failures, "total_response_time": self.total_response_time, "max_response_time": self.max_response_time, @@ -382,6 +393,7 @@ def unserialize(cls, data): "last_request_timestamp", "start_time", "num_requests", + "num_none_requests", "num_failures", "total_response_time", "max_response_time", diff --git a/locust/test/test_runners.py b/locust/test/test_runners.py index cd6ad33459..ef70f7263d 100644 --- a/locust/test/test_runners.py +++ b/locust/test/test_runners.py @@ -167,21 +167,26 @@ class MyTestLocust(Locust): master = MasterLocustRunner(MyTestLocust, self.options) server.mocked_send(Message("client_ready", None, "fake_client")) - master.stats.get("/", "GET").log(100, 23455) - master.stats.get("/", "GET").log(800, 23455) - master.stats.get("/", "GET").log(700, 23455) - master.stats.get("/", "GET").log(None, 23455) - master.stats.get("/other", "GET").log(None, 23455) + master.stats.get("/mixed", "GET").log(0, 23455) + master.stats.get("/mixed", "GET").log(800, 23455) + master.stats.get("/mixed", "GET").log(700, 23455) + master.stats.get("/mixed", "GET").log(None, 23455) + master.stats.get("/mixed", "GET").log(None, 23455) + master.stats.get("/mixed", "GET").log(None, 23455) + master.stats.get("/mixed", "GET").log(None, 23455) + master.stats.get("/onlyNone", "GET").log(None, 23455) data = {"user_count":1} events.report_to_master.fire(client_id="fake_client", data=data) master.stats.clear_all() server.mocked_send(Message("stats", data, "fake_client")) - s = master.stats.get("/", "GET") - self.assertEqual(700, s.median_response_time) - s = master.stats.get("/other", "GET") - self.assertEqual(0, s.median_response_time) + s1 = master.stats.get("/mixed", "GET") + self.assertEqual(700, s1.median_response_time) + self.assertEqual(500, s1.avg_response_time) + s2 = master.stats.get("/onlyNone", "GET") + self.assertEqual(0, s2.median_response_time) + self.assertEqual(0, s2.avg_response_time) def test_master_marks_downed_slaves_as_missing(self): class MyTestLocust(Locust): @@ -219,7 +224,43 @@ class MyTestLocust(Locust): "user_count": 2, }, "fake_client")) self.assertEqual(700, master.stats.total.median_response_time) - + + def test_master_total_stats_with_none_response_times(self): + class MyTestLocust(Locust): + pass + + with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: + master = MasterLocustRunner(MyTestLocust, self.options) + server.mocked_send(Message("client_ready", None, "fake_client")) + stats = RequestStats() + stats.log_request("GET", "/1", 100, 3546) + stats.log_request("GET", "/1", 800, 56743) + stats.log_request("GET", "/1", None, 56743) + stats2 = RequestStats() + stats2.log_request("GET", "/2", 700, 2201) + stats2.log_request("GET", "/2", None, 2201) + stats3 = RequestStats() + stats3.log_request("GET", "/3", None, 2201) + server.mocked_send(Message("stats", { + "stats":stats.serialize_stats(), + "stats_total": stats.total.serialize(), + "errors":stats.serialize_errors(), + "user_count": 1, + }, "fake_client")) + server.mocked_send(Message("stats", { + "stats":stats2.serialize_stats(), + "stats_total": stats2.total.serialize(), + "errors":stats2.serialize_errors(), + "user_count": 2, + }, "fake_client")) + server.mocked_send(Message("stats", { + "stats":stats3.serialize_stats(), + "stats_total": stats3.total.serialize(), + "errors":stats3.serialize_errors(), + "user_count": 2, + }, "fake_client")) + self.assertEqual(700, master.stats.total.median_response_time) + def test_master_current_response_times(self): class MyTestLocust(Locust): pass From 29e4ade19e64be9cbdc91f6b910e931df887a328 Mon Sep 17 00:00:00 2001 From: Lars Holmberg Date: Thu, 24 Oct 2019 16:00:27 +0200 Subject: [PATCH 4/4] Add some more basic tests of None response times. --- locust/test/test_stats.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/locust/test/test_stats.py b/locust/test/test_stats.py index 0250231ed7..444426c78d 100644 --- a/locust/test/test_stats.py +++ b/locust/test/test_stats.py @@ -19,12 +19,14 @@ def setUp(self): self.s.log(45, 0) self.s.log(135, 0) self.s.log(44, 0) + self.s.log(None, 0) self.s.log_error(Exception("dummy fail")) self.s.log_error(Exception("dummy fail")) self.s.log(375, 0) self.s.log(601, 0) self.s.log(35, 0) self.s.log(79, 0) + self.s.log(None, 0) self.s.log_error(Exception("dummy fail")) def test_percentile(self): @@ -48,17 +50,17 @@ def test_median_out_of_min_max_bounds(self): self.assertEqual(s.median_response_time, 6099) def test_total_rps(self): - self.assertEqual(self.s.total_rps, 7) + self.assertEqual(self.s.total_rps, 9) def test_current_rps(self): self.stats.total.last_request_timestamp = int(time.time()) + 4 - self.assertEqual(self.s.current_rps, 3.5) + self.assertEqual(self.s.current_rps, 4.5) self.stats.total.last_request_timestamp = int(time.time()) + 25 self.assertEqual(self.s.current_rps, 0) def test_num_reqs_fails(self): - self.assertEqual(self.s.num_requests, 7) + self.assertEqual(self.s.num_requests, 9) self.assertEqual(self.s.num_failures, 3) def test_avg(self): @@ -76,6 +78,13 @@ def test_reset(self): self.assertEqual(self.s.avg_response_time, 420.5) self.assertEqual(self.s.median_response_time, 85) + def test_avg_only_none(self): + self.s.reset() + self.s.log(None, 123) + self.assertEqual(self.s.avg_response_time, 0) + self.assertEqual(self.s.median_response_time, 0) + self.assertEqual(self.s.get_response_time_percentile(0.5), 0) + def test_reset_min_response_time(self): self.s.reset() self.s.log(756, 0)