locust 2.29.2.dev32__py3-none-any.whl → 2.29.2.dev42__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. locust/_version.py +6 -2
  2. locust/contrib/fasthttp.py +1 -1
  3. locust/dispatch.py +7 -6
  4. locust/stats.py +4 -17
  5. {locust-2.29.2.dev32.dist-info → locust-2.29.2.dev42.dist-info}/METADATA +31 -26
  6. locust-2.29.2.dev42.dist-info/RECORD +49 -0
  7. locust-2.29.2.dev42.dist-info/WHEEL +4 -0
  8. locust-2.29.2.dev42.dist-info/entry_points.txt +3 -0
  9. locust/test/__init__.py +0 -15
  10. locust/test/fake_module1_for_env_test.py +0 -7
  11. locust/test/fake_module2_for_env_test.py +0 -7
  12. locust/test/mock_locustfile.py +0 -56
  13. locust/test/mock_logging.py +0 -28
  14. locust/test/test_debugging.py +0 -39
  15. locust/test/test_dispatch.py +0 -4170
  16. locust/test/test_env.py +0 -283
  17. locust/test/test_fasthttp.py +0 -785
  18. locust/test/test_http.py +0 -325
  19. locust/test/test_interruptable_task.py +0 -48
  20. locust/test/test_load_locustfile.py +0 -228
  21. locust/test/test_locust_class.py +0 -831
  22. locust/test/test_log.py +0 -237
  23. locust/test/test_main.py +0 -2264
  24. locust/test/test_old_wait_api.py +0 -0
  25. locust/test/test_parser.py +0 -450
  26. locust/test/test_runners.py +0 -4476
  27. locust/test/test_sequential_taskset.py +0 -157
  28. locust/test/test_stats.py +0 -866
  29. locust/test/test_tags.py +0 -440
  30. locust/test/test_taskratio.py +0 -94
  31. locust/test/test_users.py +0 -69
  32. locust/test/test_util.py +0 -33
  33. locust/test/test_wait_time.py +0 -79
  34. locust/test/test_web.py +0 -1257
  35. locust/test/test_zmqrpc.py +0 -58
  36. locust/test/testcases.py +0 -248
  37. locust/test/util.py +0 -88
  38. locust-2.29.2.dev32.dist-info/RECORD +0 -79
  39. locust-2.29.2.dev32.dist-info/WHEEL +0 -5
  40. locust-2.29.2.dev32.dist-info/entry_points.txt +0 -2
  41. locust-2.29.2.dev32.dist-info/top_level.txt +0 -1
  42. {locust-2.29.2.dev32.dist-info → locust-2.29.2.dev42.dist-info}/LICENSE +0 -0
locust/test/test_stats.py DELETED
@@ -1,866 +0,0 @@
1
- import locust
2
- from locust import HttpUser, TaskSet, User, __version__, constant, task
3
- from locust.env import Environment
4
- from locust.rpc.protocol import Message
5
- from locust.stats import (
6
- PERCENTILES_TO_REPORT,
7
- STATS_NAME_WIDTH,
8
- STATS_TYPE_WIDTH,
9
- CachedResponseTimes,
10
- RequestStats,
11
- StatsCSVFileWriter,
12
- StatsEntry,
13
- diff_response_time_dicts,
14
- stats_history,
15
- )
16
- from locust.test.test_runners import mocked_rpc
17
- from locust.test.testcases import LocustTestCase, WebserverTestCase
18
- from locust.user.inspectuser import _get_task_ratio
19
-
20
- import csv
21
- import json
22
- import os
23
- import re
24
- import time
25
- import unittest
26
- from unittest import mock
27
-
28
- import gevent
29
-
30
- _TEST_CSV_STATS_INTERVAL_SEC = 0.2
31
- _TEST_CSV_STATS_INTERVAL_WAIT_SEC = _TEST_CSV_STATS_INTERVAL_SEC + 0.1
32
-
33
-
34
- def _write_csv_files(environment, stats_base_name, full_history=False):
35
- """Spawn CVS writer and exit loop after first iteration."""
36
- stats_writer = StatsCSVFileWriter(environment, PERCENTILES_TO_REPORT, stats_base_name, full_history=full_history)
37
- greenlet = gevent.spawn(stats_writer)
38
- gevent.sleep(_TEST_CSV_STATS_INTERVAL_WAIT_SEC)
39
- gevent.kill(greenlet)
40
- stats_writer.close_files()
41
-
42
-
43
- class TestRequestStats(unittest.TestCase):
44
- def setUp(self):
45
- locust.stats.PERCENTILES_TO_REPORT = PERCENTILES_TO_REPORT
46
- self.stats = RequestStats()
47
-
48
- def log(response_time, size):
49
- self.stats.log_request("GET", "test_entry", response_time, size)
50
-
51
- def log_error(exc):
52
- self.stats.log_error("GET", "test_entry", exc)
53
-
54
- log(45, 1)
55
- log(135, 1)
56
- log(44, 1)
57
- log(None, 1)
58
- log_error(Exception("dummy fail"))
59
- log_error(Exception("dummy fail"))
60
- log(375, 1)
61
- log(601, 1)
62
- log(35, 1)
63
- log(79, 1)
64
- log(None, 1)
65
- log_error(Exception("dummy fail"))
66
- self.s = self.stats.entries[("test_entry", "GET")]
67
-
68
- def test_percentile(self):
69
- s = StatsEntry(self.stats, "percentile_test", "GET")
70
- for x in range(100):
71
- s.log(x, 0)
72
-
73
- self.assertEqual(s.get_response_time_percentile(0.5), 50)
74
- self.assertEqual(s.get_response_time_percentile(0.6), 60)
75
- self.assertEqual(s.get_response_time_percentile(0.95), 95)
76
-
77
- def test_median(self):
78
- self.assertEqual(self.s.median_response_time, 79)
79
-
80
- def test_median_out_of_min_max_bounds(self):
81
- s = StatsEntry(self.stats, "median_test", "GET")
82
- s.log(6034, 0)
83
- self.assertEqual(s.median_response_time, 6034)
84
- s.reset()
85
- s.log(6099, 0)
86
- self.assertEqual(s.median_response_time, 6099)
87
-
88
- def test_total_rps(self):
89
- self.stats.log_request("GET", "other_endpoint", 1337, 1337)
90
- s2 = self.stats.entries[("other_endpoint", "GET")]
91
- s2.start_time = 2.0
92
- s2.last_request_timestamp = 6.0
93
- self.s.start_time = 1.0
94
- self.s.last_request_timestamp = 4.0
95
- self.stats.total.start_time = 1.0
96
- self.stats.total.last_request_timestamp = 6.0
97
- self.assertEqual(self.s.total_rps, 9 / 5.0)
98
- self.assertAlmostEqual(s2.total_rps, 1 / 5.0)
99
- self.assertEqual(self.stats.total.total_rps, 10 / 5.0)
100
-
101
- def test_rps_less_than_one_second(self):
102
- s = StatsEntry(self.stats, "percentile_test", "GET")
103
- for i in range(10):
104
- s.log(i, 0)
105
- self.assertGreater(s.total_rps, 10)
106
-
107
- def test_current_rps(self):
108
- self.stats.total.last_request_timestamp = int(time.time()) + 4
109
- self.assertEqual(self.s.current_rps, 4.5)
110
-
111
- self.stats.total.last_request_timestamp = int(time.time()) + 25
112
- self.assertEqual(self.s.current_rps, 0)
113
-
114
- def test_current_fail_per_sec(self):
115
- self.stats.total.last_request_timestamp = int(time.time()) + 4
116
- self.assertEqual(self.s.current_fail_per_sec, 1.5)
117
-
118
- self.stats.total.last_request_timestamp = int(time.time()) + 12
119
- self.assertEqual(self.s.current_fail_per_sec, 0.3)
120
-
121
- self.stats.total.last_request_timestamp = int(time.time()) + 25
122
- self.assertEqual(self.s.current_fail_per_sec, 0)
123
-
124
- def test_num_reqs_fails(self):
125
- self.assertEqual(self.s.num_requests, 9)
126
- self.assertEqual(self.s.num_failures, 3)
127
-
128
- def test_avg(self):
129
- self.assertEqual(self.s.avg_response_time, 187.71428571428572)
130
-
131
- def test_total_content_length(self):
132
- self.assertEqual(self.s.total_content_length, 9)
133
-
134
- def test_reset(self):
135
- self.s.reset()
136
- self.s.log(756, 0)
137
- self.s.log_error(Exception("dummy fail after reset"))
138
- self.s.log(85, 0)
139
-
140
- self.assertGreater(self.s.total_rps, 2)
141
- self.assertEqual(self.s.num_requests, 2)
142
- self.assertEqual(self.s.num_failures, 1)
143
- self.assertEqual(self.s.avg_response_time, 420.5)
144
- self.assertEqual(self.s.median_response_time, 85)
145
- self.assertNotEqual(None, self.s.last_request_timestamp)
146
- self.s.reset()
147
- self.assertEqual(None, self.s.last_request_timestamp)
148
-
149
- def test_avg_only_none(self):
150
- self.s.reset()
151
- self.s.log(None, 123)
152
- self.assertEqual(self.s.avg_response_time, 0)
153
- self.assertEqual(self.s.median_response_time, 0)
154
- self.assertEqual(self.s.get_response_time_percentile(0.5), 0)
155
-
156
- def test_reset_min_response_time(self):
157
- self.s.reset()
158
- self.s.log(756, 0)
159
- self.assertEqual(756, self.s.min_response_time)
160
-
161
- def test_aggregation(self):
162
- s1 = StatsEntry(self.stats, "aggregate me!", "GET")
163
- s1.log(12, 0)
164
- s1.log(12, 0)
165
- s1.log(38, 0)
166
- s1.log_error("Dummy exception")
167
-
168
- s2 = StatsEntry(self.stats, "aggregate me!", "GET")
169
- s2.log_error("Dummy exception")
170
- s2.log_error("Dummy exception")
171
- s2.log(12, 0)
172
- s2.log(99, 0)
173
- s2.log(14, 0)
174
- s2.log(55, 0)
175
- s2.log(38, 0)
176
- s2.log(55, 0)
177
- s2.log(97, 0)
178
-
179
- s = StatsEntry(self.stats, "GET", "")
180
- s.extend(s1)
181
- s.extend(s2)
182
-
183
- self.assertEqual(s.num_requests, 10)
184
- self.assertEqual(s.num_failures, 3)
185
- self.assertEqual(s.median_response_time, 38)
186
- self.assertEqual(s.avg_response_time, 43.2)
187
-
188
- def test_aggregation_with_rounding(self):
189
- s1 = StatsEntry(self.stats, "round me!", "GET")
190
- s1.log(122, 0) # (rounded 120) min
191
- s1.log(992, 0) # (rounded 990) max
192
- s1.log(142, 0) # (rounded 140)
193
- s1.log(552, 0) # (rounded 550)
194
- s1.log(557, 0) # (rounded 560)
195
- s1.log(387, 0) # (rounded 390)
196
- s1.log(557, 0) # (rounded 560)
197
- s1.log(977, 0) # (rounded 980)
198
-
199
- self.assertEqual(s1.num_requests, 8)
200
- self.assertEqual(s1.median_response_time, 550)
201
- self.assertEqual(s1.avg_response_time, 535.75)
202
- self.assertEqual(s1.min_response_time, 122)
203
- self.assertEqual(s1.max_response_time, 992)
204
-
205
- def test_aggregation_with_decimal_rounding(self):
206
- s1 = StatsEntry(self.stats, "round me!", "GET")
207
- s1.log(1.1, 0)
208
- s1.log(1.99, 0)
209
- s1.log(3.1, 0)
210
- self.assertEqual(s1.num_requests, 3)
211
- self.assertEqual(s1.median_response_time, 2)
212
- self.assertEqual(s1.avg_response_time, (1.1 + 1.99 + 3.1) / 3)
213
- self.assertEqual(s1.min_response_time, 1.1)
214
- self.assertEqual(s1.max_response_time, 3.1)
215
-
216
- def test_aggregation_min_response_time(self):
217
- s1 = StatsEntry(self.stats, "min", "GET")
218
- s1.log(10, 0)
219
- self.assertEqual(10, s1.min_response_time)
220
- s2 = StatsEntry(self.stats, "min", "GET")
221
- s1.extend(s2)
222
- self.assertEqual(10, s1.min_response_time)
223
-
224
- def test_aggregation_last_request_timestamp(self):
225
- s1 = StatsEntry(self.stats, "r", "GET")
226
- s2 = StatsEntry(self.stats, "r", "GET")
227
- s1.extend(s2)
228
- self.assertEqual(None, s1.last_request_timestamp)
229
- s1 = StatsEntry(self.stats, "r", "GET")
230
- s2 = StatsEntry(self.stats, "r", "GET")
231
- s1.last_request_timestamp = 666
232
- s1.extend(s2)
233
- self.assertEqual(666, s1.last_request_timestamp)
234
- s1 = StatsEntry(self.stats, "r", "GET")
235
- s2 = StatsEntry(self.stats, "r", "GET")
236
- s2.last_request_timestamp = 666
237
- s1.extend(s2)
238
- self.assertEqual(666, s1.last_request_timestamp)
239
- s1 = StatsEntry(self.stats, "r", "GET")
240
- s2 = StatsEntry(self.stats, "r", "GET")
241
- s1.last_request_timestamp = 666
242
- s1.last_request_timestamp = 700
243
- s1.extend(s2)
244
- self.assertEqual(700, s1.last_request_timestamp)
245
-
246
- def test_percentile_rounded_down(self):
247
- s1 = StatsEntry(self.stats, "rounding down!", "GET")
248
- s1.log(122, 0) # (rounded 120) min
249
- actual_percentile = s1.percentile().split()
250
-
251
- self.assertEqual(actual_percentile, ["GET", "rounding", "down!"] + ["120"] * len(PERCENTILES_TO_REPORT) + ["1"])
252
-
253
- def test_percentile_rounded_up(self):
254
- s2 = StatsEntry(self.stats, "rounding up!", "GET")
255
- s2.log(127, 0) # (rounded 130) min
256
- actual_percentile = s2.percentile().split()
257
- self.assertEqual(actual_percentile, ["GET", "rounding", "up!"] + ["130"] * len(PERCENTILES_TO_REPORT) + ["1"])
258
-
259
- def test_custom_percentile_list(self):
260
- s = StatsEntry(self.stats, "custom_percentiles", "GET")
261
- custom_percentile_list = [0.50, 0.90, 0.95, 0.99]
262
- locust.stats.PERCENTILES_TO_REPORT = custom_percentile_list
263
- s.log(150, 0)
264
- actual_percentile = s.percentile().split()
265
- self.assertEqual(
266
- actual_percentile, ["GET", "custom_percentiles"] + ["150"] * len(custom_percentile_list) + ["1"]
267
- )
268
-
269
- def test_error_grouping(self):
270
- # reset stats
271
- self.stats = RequestStats()
272
-
273
- self.stats.log_error("GET", "/some-path", Exception("Exception!"))
274
- self.stats.log_error("GET", "/some-path", Exception("Exception!"))
275
-
276
- self.assertEqual(1, len(self.stats.errors))
277
- self.assertEqual(2, list(self.stats.errors.values())[0].occurrences)
278
-
279
- self.stats.log_error("GET", "/some-path", Exception("Another exception!"))
280
- self.stats.log_error("GET", "/some-path", Exception("Another exception!"))
281
- self.stats.log_error("GET", "/some-path", Exception("Third exception!"))
282
- self.assertEqual(3, len(self.stats.errors))
283
-
284
- def test_error_grouping_errors_with_memory_addresses(self):
285
- # reset stats
286
- self.stats = RequestStats()
287
-
288
- class Dummy:
289
- pass
290
-
291
- self.stats.log_error("GET", "/", Exception(f"Error caused by {Dummy()!r}"))
292
- self.assertEqual(1, len(self.stats.errors))
293
-
294
- def test_serialize_through_message(self):
295
- """
296
- Serialize a RequestStats instance, then serialize it through a Message,
297
- and unserialize the whole thing again. This is done "IRL" when stats are sent
298
- from workers to master.
299
- """
300
- s1 = StatsEntry(self.stats, "test", "GET")
301
- s1.log(10, 0)
302
- s1.log(20, 0)
303
- s1.log(40, 0)
304
- u1 = StatsEntry.unserialize(s1.serialize())
305
-
306
- data = Message.unserialize(Message("dummy", s1.serialize(), "none").serialize()).data
307
- u1 = StatsEntry.unserialize(data)
308
-
309
- self.assertEqual(20, u1.median_response_time)
310
-
311
-
312
- class TestStatsPrinting(LocustTestCase):
313
- def setUp(self):
314
- super().setUp()
315
-
316
- self.stats = RequestStats()
317
- for i in range(100):
318
- for method, name, freq in [
319
- (
320
- "GET",
321
- "test_entry",
322
- 5,
323
- ),
324
- (
325
- "DELETE",
326
- "test" * int((STATS_NAME_WIDTH - STATS_TYPE_WIDTH + 4) / len("test")),
327
- 3,
328
- ),
329
- ]:
330
- self.stats.log_request(method, name, i, 2000 + i)
331
- if i % freq == 0:
332
- self.stats.log_error(method, name, RuntimeError(f"{method} error"))
333
-
334
- def test_print_percentile_stats(self):
335
- locust.stats.print_percentile_stats(self.stats)
336
- info = self.mocked_log.info
337
- self.assertEqual(8, len(info))
338
- self.assertEqual("Response time percentiles (approximated)", info[0])
339
- # check that headline contains same number of column as the value rows
340
- headlines = info[1].replace("# reqs", "#reqs").split()
341
- self.assertEqual(len(headlines), len(info[3].split()))
342
- self.assertEqual(len(headlines) - 1, len(info[-2].split())) # Aggregated, no "Type"
343
- self.assertEqual(info[2], info[-3]) # table ascii separators
344
-
345
- def test_print_stats(self):
346
- locust.stats.print_stats(self.stats)
347
- info = self.mocked_log.info
348
- self.assertEqual(7, len(info))
349
-
350
- headlines = info[0].replace("# ", "#").split()
351
-
352
- # check number of columns in separator
353
- self.assertEqual(len(headlines), len(info[1].split("|")) + 2)
354
- # check entry row
355
- self.assertEqual(len(headlines), len(info[2].split()))
356
- # check aggregated row, which is missing value in "type"-column
357
- self.assertEqual(len(headlines) - 1, len(info[-2].split()))
358
- # table ascii separators
359
- self.assertEqual(info[1], info[-3])
360
-
361
- def test_print_error_report(self):
362
- locust.stats.print_error_report(self.stats)
363
- info = self.mocked_log.info
364
- self.assertEqual(7, len(info))
365
- self.assertEqual("Error report", info[0])
366
-
367
- headlines = info[1].replace("# ", "#").split()
368
- # check number of columns in headlines vs table ascii separator
369
- self.assertEqual(len(headlines), len(info[2].split("|")))
370
- # table ascii separators
371
- self.assertEqual(info[2], info[-2])
372
-
373
-
374
- class TestCsvStats(LocustTestCase):
375
- STATS_BASE_NAME = "test"
376
- STATS_FILENAME = f"{STATS_BASE_NAME}_stats.csv"
377
- STATS_HISTORY_FILENAME = f"{STATS_BASE_NAME}_stats_history.csv"
378
- STATS_FAILURES_FILENAME = f"{STATS_BASE_NAME}_failures.csv"
379
- STATS_EXCEPTIONS_FILENAME = f"{STATS_BASE_NAME}_exceptions.csv"
380
-
381
- def setUp(self):
382
- super().setUp()
383
- self.remove_file_if_exists(self.STATS_FILENAME)
384
- self.remove_file_if_exists(self.STATS_HISTORY_FILENAME)
385
- self.remove_file_if_exists(self.STATS_FAILURES_FILENAME)
386
- self.remove_file_if_exists(self.STATS_EXCEPTIONS_FILENAME)
387
-
388
- def tearDown(self):
389
- self.remove_file_if_exists(self.STATS_FILENAME)
390
- self.remove_file_if_exists(self.STATS_HISTORY_FILENAME)
391
- self.remove_file_if_exists(self.STATS_FAILURES_FILENAME)
392
- self.remove_file_if_exists(self.STATS_EXCEPTIONS_FILENAME)
393
-
394
- def remove_file_if_exists(self, filename):
395
- if os.path.exists(filename):
396
- os.remove(filename)
397
-
398
- def test_write_csv_files(self):
399
- _write_csv_files(self.environment, self.STATS_BASE_NAME)
400
- self.assertTrue(os.path.exists(self.STATS_FILENAME))
401
- self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
402
- self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
403
- self.assertTrue(os.path.exists(self.STATS_EXCEPTIONS_FILENAME))
404
-
405
- def test_write_csv_files_full_history(self):
406
- _write_csv_files(self.environment, self.STATS_BASE_NAME, full_history=True)
407
- self.assertTrue(os.path.exists(self.STATS_FILENAME))
408
- self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
409
- self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
410
- self.assertTrue(os.path.exists(self.STATS_EXCEPTIONS_FILENAME))
411
-
412
- @mock.patch("locust.stats.CSV_STATS_INTERVAL_SEC", new=_TEST_CSV_STATS_INTERVAL_SEC)
413
- def test_csv_stats_writer(self):
414
- _write_csv_files(self.environment, self.STATS_BASE_NAME)
415
-
416
- self.assertTrue(os.path.exists(self.STATS_FILENAME))
417
- self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
418
- self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
419
- self.assertTrue(os.path.exists(self.STATS_EXCEPTIONS_FILENAME))
420
-
421
- with open(self.STATS_HISTORY_FILENAME) as f:
422
- reader = csv.DictReader(f)
423
- rows = [r for r in reader]
424
-
425
- self.assertEqual(2, len(rows))
426
- self.assertEqual("Aggregated", rows[0]["Name"])
427
- self.assertEqual("Aggregated", rows[1]["Name"])
428
-
429
- @mock.patch("locust.stats.CSV_STATS_INTERVAL_SEC", new=_TEST_CSV_STATS_INTERVAL_SEC)
430
- def test_csv_stats_writer_full_history(self):
431
- stats_writer = StatsCSVFileWriter(
432
- self.environment, PERCENTILES_TO_REPORT, self.STATS_BASE_NAME, full_history=True
433
- )
434
-
435
- for i in range(10):
436
- self.runner.stats.log_request("GET", "/", 100, content_length=666)
437
-
438
- greenlet = gevent.spawn(stats_writer)
439
- gevent.sleep(10)
440
-
441
- for i in range(10):
442
- self.runner.stats.log_request("GET", "/", 10, content_length=666)
443
-
444
- gevent.sleep(5)
445
-
446
- gevent.sleep(_TEST_CSV_STATS_INTERVAL_WAIT_SEC)
447
- gevent.kill(greenlet)
448
- stats_writer.close_files()
449
-
450
- self.assertTrue(os.path.exists(self.STATS_FILENAME))
451
- self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
452
- self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
453
- self.assertTrue(os.path.exists(self.STATS_EXCEPTIONS_FILENAME))
454
-
455
- with open(self.STATS_HISTORY_FILENAME) as f:
456
- reader = csv.DictReader(f)
457
- rows = [r for r in reader]
458
-
459
- self.assertGreaterEqual(len(rows), 130)
460
-
461
- self.assertEqual("/", rows[0]["Name"])
462
- self.assertEqual("Aggregated", rows[1]["Name"])
463
- self.assertEqual("/", rows[2]["Name"])
464
- self.assertEqual("Aggregated", rows[3]["Name"])
465
- self.assertEqual("20", rows[-1]["Total Request Count"])
466
-
467
- saw100 = False
468
- saw10 = False
469
-
470
- for row in rows:
471
- if not saw100 and row["95%"] == "100":
472
- saw100 = True
473
- elif saw100 and row["95%"] == "10":
474
- saw10 = True
475
- break
476
-
477
- self.assertTrue(saw100, "Never saw 95th percentile increase to 100")
478
- self.assertTrue(saw10, "Never saw 95th percentile decrease to 10")
479
-
480
- def test_csv_stats_on_master_from_aggregated_stats(self):
481
- # Failing test for: https://github.com/locustio/locust/issues/1315
482
- with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server:
483
- environment = Environment()
484
- stats_writer = StatsCSVFileWriter(
485
- environment, PERCENTILES_TO_REPORT, self.STATS_BASE_NAME, full_history=True
486
- )
487
- master = environment.create_master_runner(master_bind_host="*", master_bind_port=0)
488
- greenlet = gevent.spawn(stats_writer)
489
- gevent.sleep(_TEST_CSV_STATS_INTERVAL_WAIT_SEC)
490
-
491
- server.mocked_send(Message("client_ready", __version__, "fake_client"))
492
-
493
- master.stats.entries[("/", "GET")].log(100, 23455)
494
- master.stats.entries[("/", "GET")].log(800, 23455)
495
- master.stats.entries[("/", "GET")].log(700, 23455)
496
-
497
- data = {"user_count": 1}
498
- environment.events.report_to_master.fire(client_id="fake_client", data=data)
499
- master.stats.clear_all()
500
-
501
- server.mocked_send(Message("stats", data, "fake_client"))
502
- s = master.stats.entries[("/", "GET")]
503
- self.assertEqual(700, s.median_response_time)
504
-
505
- gevent.kill(greenlet)
506
- stats_writer.close_files()
507
-
508
- self.assertTrue(os.path.exists(self.STATS_FILENAME))
509
- self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
510
- self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
511
- self.assertTrue(os.path.exists(self.STATS_EXCEPTIONS_FILENAME))
512
-
513
- @mock.patch("locust.stats.CSV_STATS_INTERVAL_SEC", new=_TEST_CSV_STATS_INTERVAL_SEC)
514
- def test_user_count_in_csv_history_stats(self):
515
- start_time = int(time.time())
516
-
517
- class TestUser(User):
518
- wait_time = constant(10)
519
-
520
- @task
521
- def t(self):
522
- self.environment.runner.stats.log_request("GET", "/", 10, 10)
523
-
524
- environment = Environment(user_classes=[TestUser])
525
- stats_writer = StatsCSVFileWriter(environment, PERCENTILES_TO_REPORT, self.STATS_BASE_NAME, full_history=True)
526
- runner = environment.create_local_runner()
527
- # spawn a user every _TEST_CSV_STATS_INTERVAL_SEC second
528
- user_count = 15
529
- spawn_rate = 5
530
- assert 1 / 5 == _TEST_CSV_STATS_INTERVAL_SEC
531
- runner_greenlet = gevent.spawn(runner.start, user_count, spawn_rate)
532
- gevent.sleep(0.1)
533
-
534
- greenlet = gevent.spawn(stats_writer)
535
- gevent.sleep(user_count / spawn_rate)
536
- gevent.kill(greenlet)
537
- stats_writer.close_files()
538
- runner.stop()
539
- gevent.kill(runner_greenlet)
540
-
541
- with open(self.STATS_HISTORY_FILENAME) as f:
542
- reader = csv.DictReader(f)
543
- rows = [r for r in reader]
544
-
545
- self.assertEqual(2 * user_count, len(rows))
546
- for i in range(int(user_count / spawn_rate)):
547
- for _ in range(spawn_rate):
548
- row = rows.pop(0)
549
- self.assertEqual("%i" % ((i + 1) * spawn_rate), row["User Count"])
550
- self.assertEqual("/", row["Name"])
551
- self.assertEqual("%i" % ((i + 1) * spawn_rate), row["Total Request Count"])
552
- self.assertGreaterEqual(int(row["Timestamp"]), start_time)
553
- row = rows.pop(0)
554
- self.assertEqual("%i" % ((i + 1) * spawn_rate), row["User Count"])
555
- self.assertEqual("Aggregated", row["Name"])
556
- self.assertEqual("%i" % ((i + 1) * spawn_rate), row["Total Request Count"])
557
- self.assertGreaterEqual(int(row["Timestamp"]), start_time)
558
-
559
- def test_requests_csv_quote_escaping(self):
560
- with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server:
561
- environment = Environment()
562
- master = environment.create_master_runner(master_bind_host="*", master_bind_port=0)
563
- server.mocked_send(Message("client_ready", __version__, "fake_client"))
564
-
565
- request_name_dict = {
566
- "scenario": "get cashes",
567
- "path": "/cash/[amount]",
568
- "arguments": [{"size": 1}],
569
- }
570
- request_name_str = json.dumps(request_name_dict)
571
-
572
- master.stats.entries[(request_name_str, "GET")].log(100, 23455)
573
- data = {"user_count": 1}
574
- environment.events.report_to_master.fire(client_id="fake_client", data=data)
575
- master.stats.clear_all()
576
- server.mocked_send(Message("stats", data, "fake_client"))
577
-
578
- _write_csv_files(environment, self.STATS_BASE_NAME, full_history=True)
579
- with open(self.STATS_FILENAME) as f:
580
- reader = csv.DictReader(f)
581
- rows = [r for r in reader]
582
- csv_request_name = rows[0].get("Name")
583
- self.assertEqual(request_name_str, csv_request_name)
584
-
585
- def test_stats_history(self):
586
- env1 = Environment(events=locust.events, catch_exceptions=False)
587
- runner1 = env1.create_master_runner("127.0.0.1", 5558)
588
- env2 = Environment(events=locust.events, catch_exceptions=False)
589
- runner2 = env2.create_worker_runner("127.0.0.1", 5558)
590
- greenlet1 = gevent.spawn(stats_history, runner1)
591
- greenlet2 = gevent.spawn(stats_history, runner2)
592
- gevent.sleep(1)
593
- hs1 = runner1.stats.history
594
- hs2 = runner2.stats.history
595
- gevent.kill(greenlet1)
596
- gevent.kill(greenlet2)
597
- self.assertEqual(1, len(hs1))
598
- self.assertEqual(0, len(hs2))
599
-
600
-
601
- class TestStatsEntryResponseTimesCache(unittest.TestCase):
602
- def setUp(self, *args, **kwargs):
603
- super().setUp(*args, **kwargs)
604
- self.stats = RequestStats()
605
-
606
- def test_response_times_cached(self):
607
- s = StatsEntry(self.stats, "/", "GET", use_response_times_cache=True)
608
- self.assertEqual(1, len(s.response_times_cache))
609
- s.log(11, 1337)
610
- self.assertEqual(1, len(s.response_times_cache))
611
- s.last_request_timestamp -= 1
612
- s.log(666, 1337)
613
- self.assertEqual(2, len(s.response_times_cache))
614
- self.assertEqual(
615
- CachedResponseTimes(
616
- response_times={11: 1},
617
- num_requests=1,
618
- ),
619
- s.response_times_cache[int(s.last_request_timestamp) - 1],
620
- )
621
-
622
- def test_response_times_not_cached_if_not_enabled(self):
623
- s = StatsEntry(self.stats, "/", "GET")
624
- s.log(11, 1337)
625
- self.assertEqual(None, s.response_times_cache)
626
- s.last_request_timestamp -= 1
627
- s.log(666, 1337)
628
- self.assertEqual(None, s.response_times_cache)
629
-
630
- def test_latest_total_response_times_pruned(self):
631
- """
632
- Check that RequestStats.latest_total_response_times are pruned when exceeding 20 entries
633
- """
634
- s = StatsEntry(self.stats, "/", "GET", use_response_times_cache=True)
635
- t = int(time.time())
636
- for i in reversed(range(2, 30)):
637
- s.response_times_cache[t - i] = CachedResponseTimes(response_times={}, num_requests=0)
638
- self.assertEqual(29, len(s.response_times_cache))
639
- s.log(17, 1337)
640
- s.last_request_timestamp -= 1
641
- s.log(1, 1)
642
- self.assertEqual(20, len(s.response_times_cache))
643
- self.assertEqual(
644
- CachedResponseTimes(response_times={17: 1}, num_requests=1),
645
- s.response_times_cache.popitem(last=True)[1],
646
- )
647
-
648
- def test_get_current_response_time_percentile(self):
649
- s = StatsEntry(self.stats, "/", "GET", use_response_times_cache=True)
650
- t = int(time.time())
651
- s.response_times_cache[t - 10] = CachedResponseTimes(
652
- response_times={i: 1 for i in range(100)}, num_requests=200
653
- )
654
- s.response_times_cache[t - 10].response_times[1] = 201
655
-
656
- s.response_times = {i: 2 for i in range(100)}
657
- s.response_times[1] = 202
658
- s.num_requests = 300
659
-
660
- self.assertEqual(95, s.get_current_response_time_percentile(0.95))
661
-
662
- def test_get_current_response_time_percentile_outside_cache_window(self):
663
- s = StatsEntry(self.stats, "/", "GET", use_response_times_cache=True)
664
- # an empty response times cache, current time will not be in this cache
665
- s.response_times_cache = {}
666
- self.assertEqual(None, s.get_current_response_time_percentile(0.95))
667
-
668
- def test_diff_response_times_dicts(self):
669
- self.assertEqual(
670
- {1: 5, 6: 8},
671
- diff_response_time_dicts(
672
- {1: 6, 6: 16, 2: 2},
673
- {1: 1, 6: 8, 2: 2},
674
- ),
675
- )
676
- self.assertEqual(
677
- {},
678
- diff_response_time_dicts(
679
- {},
680
- {},
681
- ),
682
- )
683
- self.assertEqual(
684
- {10: 15},
685
- diff_response_time_dicts(
686
- {10: 15},
687
- {},
688
- ),
689
- )
690
- self.assertEqual(
691
- {10: 10},
692
- diff_response_time_dicts(
693
- {10: 10},
694
- {},
695
- ),
696
- )
697
- self.assertEqual(
698
- {},
699
- diff_response_time_dicts(
700
- {1: 1},
701
- {1: 1},
702
- ),
703
- )
704
-
705
-
706
- class TestStatsEntry(unittest.TestCase):
707
- def parse_string_output(self, text):
708
- tokenlist = re.split(r"[\s\(\)%|]+", text.strip())
709
- tokens = {
710
- "method": tokenlist[0],
711
- "name": tokenlist[1],
712
- "request_count": int(tokenlist[2]),
713
- "failure_count": int(tokenlist[3]),
714
- "failure_percentage": float(tokenlist[4]),
715
- }
716
- return tokens
717
-
718
- def setUp(self, *args, **kwargs):
719
- super().setUp(*args, **kwargs)
720
- self.stats = RequestStats()
721
-
722
- def test_fail_ratio_with_no_failures(self):
723
- REQUEST_COUNT = 10
724
- FAILURE_COUNT = 0
725
- EXPECTED_FAIL_RATIO = 0.0
726
-
727
- s = StatsEntry(self.stats, "/", "GET")
728
- s.num_requests = REQUEST_COUNT
729
- s.num_failures = FAILURE_COUNT
730
-
731
- self.assertAlmostEqual(s.fail_ratio, EXPECTED_FAIL_RATIO)
732
- output_fields = self.parse_string_output(str(s))
733
- self.assertEqual(output_fields["request_count"], REQUEST_COUNT)
734
- self.assertEqual(output_fields["failure_count"], FAILURE_COUNT)
735
- self.assertAlmostEqual(output_fields["failure_percentage"], EXPECTED_FAIL_RATIO * 100)
736
-
737
- def test_fail_ratio_with_all_failures(self):
738
- REQUEST_COUNT = 10
739
- FAILURE_COUNT = 10
740
- EXPECTED_FAIL_RATIO = 1.0
741
-
742
- s = StatsEntry(self.stats, "/", "GET")
743
- s.num_requests = REQUEST_COUNT
744
- s.num_failures = FAILURE_COUNT
745
-
746
- self.assertAlmostEqual(s.fail_ratio, EXPECTED_FAIL_RATIO)
747
- output_fields = self.parse_string_output(str(s))
748
- self.assertEqual(output_fields["request_count"], REQUEST_COUNT)
749
- self.assertEqual(output_fields["failure_count"], FAILURE_COUNT)
750
- self.assertAlmostEqual(output_fields["failure_percentage"], EXPECTED_FAIL_RATIO * 100)
751
-
752
- def test_fail_ratio_with_half_failures(self):
753
- REQUEST_COUNT = 10
754
- FAILURE_COUNT = 5
755
- EXPECTED_FAIL_RATIO = 0.5
756
-
757
- s = StatsEntry(self.stats, "/", "GET")
758
- s.num_requests = REQUEST_COUNT
759
- s.num_failures = FAILURE_COUNT
760
-
761
- self.assertAlmostEqual(s.fail_ratio, EXPECTED_FAIL_RATIO)
762
- output_fields = self.parse_string_output(str(s))
763
- self.assertEqual(output_fields["request_count"], REQUEST_COUNT)
764
- self.assertEqual(output_fields["failure_count"], FAILURE_COUNT)
765
- self.assertAlmostEqual(output_fields["failure_percentage"], EXPECTED_FAIL_RATIO * 100)
766
-
767
-
768
- class TestRequestStatsWithWebserver(WebserverTestCase):
769
- def setUp(self):
770
- super().setUp()
771
-
772
- class MyUser(HttpUser):
773
- host = "http://127.0.0.1:%i" % self.port
774
-
775
- self.locust = MyUser(self.environment)
776
-
777
- def test_request_stats_content_length(self):
778
- self.locust.client.get("/ultra_fast")
779
- self.assertEqual(
780
- self.runner.stats.entries[("/ultra_fast", "GET")].avg_content_length, len("This is an ultra fast response")
781
- )
782
- self.locust.client.get("/ultra_fast")
783
- # test legacy stats.get() function sometimes too
784
- self.assertEqual(
785
- self.runner.stats.get("/ultra_fast", "GET").avg_content_length, len("This is an ultra fast response")
786
- )
787
-
788
- def test_request_stats_no_content_length(self):
789
- path = "/no_content_length"
790
- self.locust.client.get(path)
791
- self.assertEqual(
792
- self.runner.stats.entries[(path, "GET")].avg_content_length,
793
- len("This response does not have content-length in the header"),
794
- )
795
-
796
- def test_request_stats_no_content_length_streaming(self):
797
- path = "/no_content_length"
798
- self.locust.client.get(path, stream=True)
799
- self.assertEqual(0, self.runner.stats.entries[(path, "GET")].avg_content_length)
800
-
801
- def test_request_stats_named_endpoint(self):
802
- self.locust.client.get("/ultra_fast", name="my_custom_name")
803
- self.assertEqual(1, self.runner.stats.entries[("my_custom_name", "GET")].num_requests)
804
-
805
- def test_request_stats_named_endpoint_request_name(self):
806
- self.locust.client.request_name = "my_custom_name_1"
807
- self.locust.client.get("/ultra_fast")
808
- self.assertEqual(1, self.runner.stats.entries[("my_custom_name_1", "GET")].num_requests)
809
- self.locust.client.request_name = None
810
-
811
- def test_request_stats_named_endpoint_rename_request(self):
812
- with self.locust.client.rename_request("my_custom_name_3"):
813
- self.locust.client.get("/ultra_fast")
814
- self.assertEqual(1, self.runner.stats.entries[("my_custom_name_3", "GET")].num_requests)
815
-
816
- def test_request_stats_query_variables(self):
817
- self.locust.client.get("/ultra_fast?query=1")
818
- self.assertEqual(1, self.runner.stats.entries[("/ultra_fast?query=1", "GET")].num_requests)
819
-
820
- def test_request_stats_put(self):
821
- self.locust.client.put("/put")
822
- self.assertEqual(1, self.runner.stats.entries[("/put", "PUT")].num_requests)
823
-
824
- def test_request_connection_error(self):
825
- class MyUser(HttpUser):
826
- host = "http://localhost:1"
827
-
828
- locust = MyUser(self.environment)
829
- response = locust.client.get("/", timeout=0.1)
830
- self.assertEqual(response.status_code, 0)
831
- self.assertEqual(1, self.runner.stats.entries[("/", "GET")].num_failures)
832
- self.assertEqual(1, self.runner.stats.entries[("/", "GET")].num_requests)
833
-
834
-
835
- class MyTaskSet(TaskSet):
836
- @task(75)
837
- def root_task(self):
838
- pass
839
-
840
- @task(25)
841
- class MySubTaskSet(TaskSet):
842
- @task
843
- def task1(self):
844
- pass
845
-
846
- @task
847
- def task2(self):
848
- pass
849
-
850
-
851
- class TestInspectUser(unittest.TestCase):
852
- def test_get_task_ratio_relative(self):
853
- ratio = _get_task_ratio([MyTaskSet], False, 1.0)
854
- self.assertEqual(1.0, ratio["MyTaskSet"]["ratio"])
855
- self.assertEqual(0.75, ratio["MyTaskSet"]["tasks"]["root_task"]["ratio"])
856
- self.assertEqual(0.25, ratio["MyTaskSet"]["tasks"]["MySubTaskSet"]["ratio"])
857
- self.assertEqual(0.5, ratio["MyTaskSet"]["tasks"]["MySubTaskSet"]["tasks"]["task1"]["ratio"])
858
- self.assertEqual(0.5, ratio["MyTaskSet"]["tasks"]["MySubTaskSet"]["tasks"]["task2"]["ratio"])
859
-
860
- def test_get_task_ratio_total(self):
861
- ratio = _get_task_ratio([MyTaskSet], True, 1.0)
862
- self.assertEqual(1.0, ratio["MyTaskSet"]["ratio"])
863
- self.assertEqual(0.75, ratio["MyTaskSet"]["tasks"]["root_task"]["ratio"])
864
- self.assertEqual(0.25, ratio["MyTaskSet"]["tasks"]["MySubTaskSet"]["ratio"])
865
- self.assertEqual(0.125, ratio["MyTaskSet"]["tasks"]["MySubTaskSet"]["tasks"]["task1"]["ratio"])
866
- self.assertEqual(0.125, ratio["MyTaskSet"]["tasks"]["MySubTaskSet"]["tasks"]["task2"]["ratio"])