locust 2.28.1.dev49__py3-none-any.whl → 2.28.1.dev66__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.
- locust/_version.py +2 -2
- locust/env.py +6 -0
- locust/log.py +11 -1
- locust/runners.py +28 -1
- locust/test/test_runners.py +119 -40
- locust/test/test_web.py +19 -1
- locust/web.py +2 -11
- locust/webui/dist/assets/index-84c63e70.js +250 -0
- locust/webui/dist/auth.html +1 -1
- locust/webui/dist/index.html +1 -1
- {locust-2.28.1.dev49.dist-info → locust-2.28.1.dev66.dist-info}/METADATA +1 -1
- {locust-2.28.1.dev49.dist-info → locust-2.28.1.dev66.dist-info}/RECORD +16 -15
- {locust-2.28.1.dev49.dist-info → locust-2.28.1.dev66.dist-info}/LICENSE +0 -0
- {locust-2.28.1.dev49.dist-info → locust-2.28.1.dev66.dist-info}/WHEEL +0 -0
- {locust-2.28.1.dev49.dist-info → locust-2.28.1.dev66.dist-info}/entry_points.txt +0 -0
- {locust-2.28.1.dev49.dist-info → locust-2.28.1.dev66.dist-info}/top_level.txt +0 -0
locust/_version.py
CHANGED
@@ -12,5 +12,5 @@ __version__: str
|
|
12
12
|
__version_tuple__: VERSION_TUPLE
|
13
13
|
version_tuple: VERSION_TUPLE
|
14
14
|
|
15
|
-
__version__ = version = '2.28.1.
|
16
|
-
__version_tuple__ = version_tuple = (2, 28, 1, '
|
15
|
+
__version__ = version = '2.28.1.dev66'
|
16
|
+
__version_tuple__ = version_tuple = (2, 28, 1, 'dev66')
|
locust/env.py
CHANGED
@@ -99,6 +99,8 @@ class Environment:
|
|
99
99
|
"""List of the available Tasks per User Classes to pick from in the Task Picker"""
|
100
100
|
self.dispatcher_class = dispatcher_class
|
101
101
|
"""A user dispatcher class that decides how users are spawned, default :class:`UsersDispatcher <locust.dispatch.UsersDispatcher>`"""
|
102
|
+
self.worker_logs: dict[str, list[str]] = {}
|
103
|
+
"""Captured logs from all connected workers"""
|
102
104
|
|
103
105
|
self._remove_user_classes_with_weight_zero()
|
104
106
|
self._validate_user_class_name_uniqueness()
|
@@ -209,6 +211,10 @@ class Environment:
|
|
209
211
|
if key == "tasks":
|
210
212
|
user_class.tasks = [task for task in user_tasks if task.__name__ in value]
|
211
213
|
|
214
|
+
def update_worker_logs(self, worker_log_report):
|
215
|
+
if worker_log_report.get("worker_id", None):
|
216
|
+
self.worker_logs[worker_log_report.get("worker_id")] = worker_log_report.get("logs", [])
|
217
|
+
|
212
218
|
def _filter_tasks_by_tags(self) -> None:
|
213
219
|
"""
|
214
220
|
Filter the tasks on all the user_classes recursively, according to the tags and
|
locust/log.py
CHANGED
@@ -2,6 +2,7 @@ import logging
|
|
2
2
|
import logging.config
|
3
3
|
import re
|
4
4
|
import socket
|
5
|
+
from collections import deque
|
5
6
|
|
6
7
|
HOSTNAME = re.sub(r"\..*", "", socket.gethostname())
|
7
8
|
|
@@ -13,7 +14,7 @@ unhandled_greenlet_exception = False
|
|
13
14
|
class LogReader(logging.Handler):
|
14
15
|
def __init__(self):
|
15
16
|
super().__init__()
|
16
|
-
self.logs =
|
17
|
+
self.logs = deque(maxlen=500)
|
17
18
|
|
18
19
|
def emit(self, record):
|
19
20
|
self.logs.append(self.format(record))
|
@@ -75,6 +76,15 @@ def setup_logging(loglevel, logfile=None):
|
|
75
76
|
logging.config.dictConfig(LOGGING_CONFIG)
|
76
77
|
|
77
78
|
|
79
|
+
def get_logs():
|
80
|
+
log_reader_handler = [handler for handler in logging.getLogger("root").handlers if handler.name == "log_reader"]
|
81
|
+
|
82
|
+
if log_reader_handler:
|
83
|
+
return list(log_reader_handler[0].logs)
|
84
|
+
|
85
|
+
return []
|
86
|
+
|
87
|
+
|
78
88
|
def greenlet_exception_logger(logger, level=logging.CRITICAL):
|
79
89
|
"""
|
80
90
|
Return a function that can be used as argument to Greenlet.link_exception() that will log the
|
locust/runners.py
CHANGED
@@ -39,7 +39,7 @@ from gevent.pool import Group
|
|
39
39
|
from . import argument_parser
|
40
40
|
from .dispatch import UsersDispatcher
|
41
41
|
from .exception import RPCError, RPCReceiveError, RPCSendError
|
42
|
-
from .log import greenlet_exception_logger
|
42
|
+
from .log import get_logs, greenlet_exception_logger
|
43
43
|
from .rpc import (
|
44
44
|
Message,
|
45
45
|
rpc,
|
@@ -66,6 +66,7 @@ STATE_INIT, STATE_SPAWNING, STATE_RUNNING, STATE_CLEANUP, STATE_STOPPING, STATE_
|
|
66
66
|
"missing",
|
67
67
|
]
|
68
68
|
WORKER_REPORT_INTERVAL = 3.0
|
69
|
+
WORKER_LOG_REPORT_INTERVAL = 10
|
69
70
|
CPU_MONITOR_INTERVAL = 5.0
|
70
71
|
CPU_WARNING_THRESHOLD = 90
|
71
72
|
HEARTBEAT_INTERVAL = 1
|
@@ -1116,6 +1117,8 @@ class MasterRunner(DistributedRunner):
|
|
1116
1117
|
# a worker finished spawning (this happens multiple times during rampup)
|
1117
1118
|
self.clients[msg.node_id].state = STATE_RUNNING
|
1118
1119
|
self.clients[msg.node_id].user_classes_count = msg.data["user_classes_count"]
|
1120
|
+
elif msg.type == "logs":
|
1121
|
+
self.environment.update_worker_logs(msg.data)
|
1119
1122
|
elif msg.type == "quit":
|
1120
1123
|
if msg.node_id in self.clients:
|
1121
1124
|
client = self.clients[msg.node_id]
|
@@ -1212,6 +1215,7 @@ class WorkerRunner(DistributedRunner):
|
|
1212
1215
|
self.client_id = socket.gethostname() + "_" + uuid4().hex
|
1213
1216
|
self.master_host = master_host
|
1214
1217
|
self.master_port = master_port
|
1218
|
+
self.logs: list[str] = []
|
1215
1219
|
self.worker_cpu_warning_emitted = False
|
1216
1220
|
self._users_dispatcher: UsersDispatcher | None = None
|
1217
1221
|
self.client = rpc.Client(master_host, master_port, self.client_id)
|
@@ -1220,6 +1224,7 @@ class WorkerRunner(DistributedRunner):
|
|
1220
1224
|
self.greenlet.spawn(self.heartbeat).link_exception(greenlet_exception_handler)
|
1221
1225
|
self.greenlet.spawn(self.heartbeat_timeout_checker).link_exception(greenlet_exception_handler)
|
1222
1226
|
self.greenlet.spawn(self.stats_reporter).link_exception(greenlet_exception_handler)
|
1227
|
+
self.greenlet.spawn(self.logs_reporter).link_exception(greenlet_exception_handler)
|
1223
1228
|
|
1224
1229
|
# register listener that adds the current number of spawned users to the report that is sent to the master node
|
1225
1230
|
def on_report_to_master(client_id: str, data: dict[str, Any]):
|
@@ -1417,6 +1422,25 @@ class WorkerRunner(DistributedRunner):
|
|
1417
1422
|
logger.error(f"Temporary connection lost to master server: {e}, will retry later.")
|
1418
1423
|
gevent.sleep(WORKER_REPORT_INTERVAL)
|
1419
1424
|
|
1425
|
+
def logs_reporter(self) -> None:
|
1426
|
+
if WORKER_LOG_REPORT_INTERVAL < 0:
|
1427
|
+
return
|
1428
|
+
|
1429
|
+
while True:
|
1430
|
+
current_logs = get_logs()
|
1431
|
+
|
1432
|
+
if (len(current_logs) - len(self.logs)) > 10:
|
1433
|
+
logger.warning(
|
1434
|
+
"The worker attempted to send more than 10 log lines in one interval. Further log sending was disabled for this worker."
|
1435
|
+
)
|
1436
|
+
self._send_logs(get_logs())
|
1437
|
+
break
|
1438
|
+
if len(current_logs) > len(self.logs):
|
1439
|
+
self._send_logs(current_logs)
|
1440
|
+
|
1441
|
+
self.logs = current_logs
|
1442
|
+
gevent.sleep(WORKER_LOG_REPORT_INTERVAL)
|
1443
|
+
|
1420
1444
|
def send_message(self, msg_type: str, data: dict[str, Any] | None = None, client_id: str | None = None) -> None:
|
1421
1445
|
"""
|
1422
1446
|
Sends a message to master node
|
@@ -1433,6 +1457,9 @@ class WorkerRunner(DistributedRunner):
|
|
1433
1457
|
self.environment.events.report_to_master.fire(client_id=self.client_id, data=data)
|
1434
1458
|
self.client.send(Message("stats", data, self.client_id))
|
1435
1459
|
|
1460
|
+
def _send_logs(self, current_logs) -> None:
|
1461
|
+
self.send_message("logs", {"worker_id": self.client_id, "logs": current_logs})
|
1462
|
+
|
1436
1463
|
def connect_to_master(self):
|
1437
1464
|
self.retry += 1
|
1438
1465
|
self.client.send(Message("client_ready", __version__, self.client_id))
|
locust/test/test_runners.py
CHANGED
@@ -11,6 +11,7 @@ from locust.argument_parser import parse_options
|
|
11
11
|
from locust.dispatch import UsersDispatcher
|
12
12
|
from locust.env import Environment
|
13
13
|
from locust.exception import RPCError, RPCReceiveError, StopUser
|
14
|
+
from locust.log import LogReader
|
14
15
|
from locust.main import create_environment
|
15
16
|
from locust.rpc import Message
|
16
17
|
from locust.runners import (
|
@@ -32,6 +33,7 @@ from locust.user import (
|
|
32
33
|
)
|
33
34
|
|
34
35
|
import json
|
36
|
+
import logging
|
35
37
|
import random
|
36
38
|
import time
|
37
39
|
import unittest
|
@@ -80,11 +82,12 @@ def mocked_rpc(raise_on_close=True):
|
|
80
82
|
self.outbox.append(message)
|
81
83
|
|
82
84
|
def send_to_client(self, message):
|
83
|
-
|
85
|
+
print(message)
|
86
|
+
self.outbox.append(message)
|
84
87
|
|
85
88
|
@classmethod
|
86
89
|
def get_messages(cls, message_type=None) -> list:
|
87
|
-
return [message
|
90
|
+
return [message for message in cls.outbox if message_type is None or message.type == message_type]
|
88
91
|
|
89
92
|
def recv_from_client(self):
|
90
93
|
results = self.queue.get()
|
@@ -2601,9 +2604,9 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
2601
2604
|
server.mocked_send(Message("client_ready", __version__, "zeh_fake_client2"))
|
2602
2605
|
self.assertEqual(2, len(master.clients))
|
2603
2606
|
sleep(0.1) # give time for messages to be sent to clients
|
2604
|
-
spawn_messages =
|
2605
|
-
self.assertEqual({"TestUser": 50}, spawn_messages[-1]
|
2606
|
-
self.assertEqual({"TestUser": 50}, spawn_messages[-2]
|
2607
|
+
spawn_messages = server.get_messages("spawn")
|
2608
|
+
self.assertEqual({"TestUser": 50}, spawn_messages[-1].data["user_classes_count"])
|
2609
|
+
self.assertEqual({"TestUser": 50}, spawn_messages[-2].data["user_classes_count"])
|
2607
2610
|
|
2608
2611
|
def test_sends_spawn_data_to_ready_running_spawning_workers(self):
|
2609
2612
|
"""Sends spawn job to running, ready, or spawning workers"""
|
@@ -2648,7 +2651,7 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
2648
2651
|
server.mocked_send(Message("client_ready", __version__, "fake_client%i" % i))
|
2649
2652
|
|
2650
2653
|
master.start(7, 7)
|
2651
|
-
self.assertEqual(15, len(server.
|
2654
|
+
self.assertEqual(15, len(server.get_messages()))
|
2652
2655
|
self.assertEqual(1, run_count[0])
|
2653
2656
|
|
2654
2657
|
# change number of users and check that test_start isn't fired again
|
@@ -2687,7 +2690,7 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
2687
2690
|
server.mocked_send(Message("client_ready", __version__, "fake_client%i" % i))
|
2688
2691
|
|
2689
2692
|
master.start(7, 7)
|
2690
|
-
self.assertEqual(15, len(server.
|
2693
|
+
self.assertEqual(15, len(server.get_messages()))
|
2691
2694
|
master.stop()
|
2692
2695
|
self.assertTrue(self.runner_stopping)
|
2693
2696
|
self.assertTrue(self.runner_stopped)
|
@@ -2726,7 +2729,7 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
2726
2729
|
server.mocked_send(Message("client_ready", __version__, "fake_client%i" % i))
|
2727
2730
|
|
2728
2731
|
master.start(7, 7)
|
2729
|
-
self.assertEqual(15, len(server.
|
2732
|
+
self.assertEqual(15, len(server.get_messages()))
|
2730
2733
|
master.quit()
|
2731
2734
|
self.assertTrue(self.runner_stopping)
|
2732
2735
|
self.assertTrue(self.runner_stopped)
|
@@ -2819,9 +2822,8 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
2819
2822
|
self.assertEqual(USERS_COUNT * 3, len(server.outbox))
|
2820
2823
|
|
2821
2824
|
indexes = []
|
2822
|
-
for
|
2823
|
-
|
2824
|
-
indexes.append(msg.data["index"])
|
2825
|
+
for msg in server.get_messages("ack"):
|
2826
|
+
indexes.append(msg.data["index"])
|
2825
2827
|
self.assertEqual(USERS_COUNT, len(indexes), "Total number of locusts/workers is not 5")
|
2826
2828
|
|
2827
2829
|
indexes.sort()
|
@@ -3110,8 +3112,10 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
3110
3112
|
master.clients[i] = WorkerNode(str(i))
|
3111
3113
|
master.send_message("test_custom_msg", {"test_data": 123})
|
3112
3114
|
|
3113
|
-
|
3114
|
-
|
3115
|
+
messages = server.get_messages()
|
3116
|
+
|
3117
|
+
self.assertEqual(5, len(messages))
|
3118
|
+
for msg in messages:
|
3115
3119
|
self.assertEqual("test_custom_msg", msg.type)
|
3116
3120
|
self.assertEqual(123, msg.data["test_data"])
|
3117
3121
|
|
@@ -3198,10 +3202,12 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
3198
3202
|
master = self.get_runner()
|
3199
3203
|
server.mocked_send(Message("client_ready", __version__, "dummy_client"))
|
3200
3204
|
|
3205
|
+
messages = server.get_messages()
|
3206
|
+
|
3201
3207
|
self.assertEqual(1, len(master.clients))
|
3202
|
-
self.assertEqual("ack",
|
3203
|
-
self.assertEqual(1, len(
|
3204
|
-
self.assertEqual(0,
|
3208
|
+
self.assertEqual("ack", messages[0].type)
|
3209
|
+
self.assertEqual(1, len(messages))
|
3210
|
+
self.assertEqual(0, messages[0].data["index"])
|
3205
3211
|
|
3206
3212
|
def test_worker_sends_bad_message_to_master(self):
|
3207
3213
|
"""
|
@@ -3224,11 +3230,12 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
3224
3230
|
master.start(10, 10)
|
3225
3231
|
sleep(0.1)
|
3226
3232
|
server.mocked_send(Message("stats", BAD_MESSAGE, "zeh_fake_client1"))
|
3227
|
-
|
3233
|
+
messages = server.get_messages()
|
3234
|
+
self.assertEqual(5, len(messages))
|
3228
3235
|
|
3229
3236
|
# Expected message order in outbox: ack, spawn, reconnect, ack
|
3230
3237
|
self.assertEqual(
|
3231
|
-
"reconnect",
|
3238
|
+
"reconnect", messages[3].type, "Master didn't send worker reconnect message when expected."
|
3232
3239
|
)
|
3233
3240
|
|
3234
3241
|
def test_worker_sends_unrecognized_message_to_master(self):
|
@@ -3252,7 +3259,7 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
3252
3259
|
master.start(10, 10)
|
3253
3260
|
sleep(0.1)
|
3254
3261
|
server.mocked_send(Message("stats", UNRECOGNIZED_MESSAGE, "zeh_fake_client1"))
|
3255
|
-
self.assertEqual(3, len(server.
|
3262
|
+
self.assertEqual(3, len(server.get_messages()))
|
3256
3263
|
|
3257
3264
|
def test_unknown_host_sends_message_to_master(self):
|
3258
3265
|
"""
|
@@ -3275,7 +3282,7 @@ class TestMasterRunner(LocustRunnerTestCase):
|
|
3275
3282
|
master.start(10, 10)
|
3276
3283
|
sleep(0.1)
|
3277
3284
|
server.mocked_send(Message("stats", UNRECOGNIZED_HOST_MESSAGE, "unknown_host"))
|
3278
|
-
self.assertEqual(3, len(server.
|
3285
|
+
self.assertEqual(3, len(server.get_messages()))
|
3279
3286
|
|
3280
3287
|
|
3281
3288
|
class TestWorkerRunner(LocustTestCase):
|
@@ -3308,8 +3315,9 @@ class TestWorkerRunner(LocustTestCase):
|
|
3308
3315
|
|
3309
3316
|
with mock.patch("locust.rpc.rpc.Client", mocked_rpc()) as client:
|
3310
3317
|
worker = self.get_runner(environment=Environment(), user_classes=[MyTestUser], client=client)
|
3311
|
-
|
3312
|
-
self.assertEqual(
|
3318
|
+
messages = client.get_messages()
|
3319
|
+
self.assertEqual(1, len(messages))
|
3320
|
+
self.assertEqual("client_ready", messages[0].type)
|
3313
3321
|
client.mocked_send(
|
3314
3322
|
Message(
|
3315
3323
|
"spawn",
|
@@ -3323,8 +3331,9 @@ class TestWorkerRunner(LocustTestCase):
|
|
3323
3331
|
"dummy_client_id",
|
3324
3332
|
)
|
3325
3333
|
)
|
3334
|
+
self.assertTrue(client.get_messages("spawning"))
|
3326
3335
|
# wait for worker to spawn locusts
|
3327
|
-
self.assertIn("spawning", [m.type for m in
|
3336
|
+
# self.assertIn("spawning", [m.type for m in messages])
|
3328
3337
|
worker.spawning_greenlet.join()
|
3329
3338
|
self.assertEqual(1, len(worker.user_greenlets))
|
3330
3339
|
# check that locust has started running
|
@@ -3348,8 +3357,9 @@ class TestWorkerRunner(LocustTestCase):
|
|
3348
3357
|
|
3349
3358
|
with mock.patch("locust.rpc.rpc.Client", mocked_rpc()) as client:
|
3350
3359
|
worker = self.get_runner(environment=Environment(), user_classes=[MyTestUser], client=client)
|
3351
|
-
|
3352
|
-
self.assertEqual(
|
3360
|
+
messages = client.get_messages()
|
3361
|
+
self.assertEqual(1, len(messages))
|
3362
|
+
self.assertEqual("client_ready", messages[0].type)
|
3353
3363
|
client.mocked_send(
|
3354
3364
|
Message(
|
3355
3365
|
"spawn",
|
@@ -3365,7 +3375,7 @@ class TestWorkerRunner(LocustTestCase):
|
|
3365
3375
|
)
|
3366
3376
|
# print("outbox:", client.outbox)
|
3367
3377
|
# wait for worker to spawn locusts
|
3368
|
-
self.
|
3378
|
+
self.assertTrue(client.get_messages("spawning"))
|
3369
3379
|
worker.spawning_greenlet.join()
|
3370
3380
|
self.assertEqual(1, len(worker.user_greenlets))
|
3371
3381
|
# check that locust has started running
|
@@ -3509,14 +3519,14 @@ class TestWorkerRunner(LocustTestCase):
|
|
3509
3519
|
|
3510
3520
|
sleep(2)
|
3511
3521
|
|
3512
|
-
message =
|
3522
|
+
message = client.get_messages("stats")[-1]
|
3513
3523
|
self.assertIsNotNone(message)
|
3514
3524
|
self.assertIn("user_count", message.data)
|
3515
3525
|
self.assertIn("user_classes_count", message.data)
|
3516
3526
|
self.assertEqual(message.data["user_count"], 10)
|
3517
3527
|
self.assertEqual(message.data["user_classes_count"]["MyUser"], 10)
|
3518
3528
|
|
3519
|
-
message =
|
3529
|
+
message = client.get_messages("spawning_complete")[0]
|
3520
3530
|
self.assertIsNotNone(message)
|
3521
3531
|
self.assertIn("user_count", message.data)
|
3522
3532
|
self.assertIn("user_classes_count", message.data)
|
@@ -3541,11 +3551,11 @@ class TestWorkerRunner(LocustTestCase):
|
|
3541
3551
|
worker = self.get_runner(environment=Environment(), user_classes=[MyUser], client=client)
|
3542
3552
|
|
3543
3553
|
t0 = time.perf_counter()
|
3544
|
-
while len(
|
3554
|
+
while len(client.get_messages("heartbeat")) == 0:
|
3545
3555
|
self.assertLessEqual(time.perf_counter() - t0, 3)
|
3546
3556
|
sleep(0.1)
|
3547
3557
|
|
3548
|
-
message =
|
3558
|
+
message = client.get_messages("heartbeat")[-1]
|
3549
3559
|
self.assertEqual(len(message.data), 3)
|
3550
3560
|
self.assertIn("state", message.data)
|
3551
3561
|
self.assertIn("current_cpu_usage", message.data)
|
@@ -3718,8 +3728,9 @@ class TestWorkerRunner(LocustTestCase):
|
|
3718
3728
|
worker = self.get_runner(environment=Environment(), user_classes=[MyUser], client=client)
|
3719
3729
|
client.outbox.clear()
|
3720
3730
|
worker.send_message("test_custom_msg", {"test_data": 123})
|
3721
|
-
|
3722
|
-
self.assertEqual(
|
3731
|
+
messages = client.get_messages()
|
3732
|
+
self.assertEqual("test_custom_msg", messages[0].type)
|
3733
|
+
self.assertEqual(123, messages[0].data["test_data"])
|
3723
3734
|
worker.quit()
|
3724
3735
|
|
3725
3736
|
def test_custom_message_receive(self):
|
@@ -3790,8 +3801,9 @@ class TestWorkerRunner(LocustTestCase):
|
|
3790
3801
|
run_count[0] += 1
|
3791
3802
|
|
3792
3803
|
worker = self.get_runner(environment=environment, user_classes=[MyTestUser], client=client)
|
3793
|
-
|
3794
|
-
self.assertEqual(
|
3804
|
+
messages = client.get_messages()
|
3805
|
+
self.assertEqual(1, len(messages))
|
3806
|
+
self.assertEqual("client_ready", messages[0].type)
|
3795
3807
|
client.mocked_send(
|
3796
3808
|
Message(
|
3797
3809
|
"spawn",
|
@@ -3808,7 +3820,7 @@ class TestWorkerRunner(LocustTestCase):
|
|
3808
3820
|
)
|
3809
3821
|
)
|
3810
3822
|
# wait for worker to spawn locusts
|
3811
|
-
self.
|
3823
|
+
self.assertTrue(client.get_messages("spawning"))
|
3812
3824
|
worker.spawning_greenlet.join()
|
3813
3825
|
self.assertEqual(1, len(worker.user_greenlets))
|
3814
3826
|
self.assertEqual(1, run_count[0])
|
@@ -3876,8 +3888,9 @@ class TestWorkerRunner(LocustTestCase):
|
|
3876
3888
|
run_count[0] += 1
|
3877
3889
|
|
3878
3890
|
worker = self.get_runner(environment=environment, user_classes=[MyTestUser], client=client)
|
3879
|
-
|
3880
|
-
self.assertEqual(
|
3891
|
+
messages = client.get_messages()
|
3892
|
+
self.assertEqual(1, len(messages))
|
3893
|
+
self.assertEqual("client_ready", messages[0].type)
|
3881
3894
|
client.mocked_send(
|
3882
3895
|
Message(
|
3883
3896
|
"spawn",
|
@@ -3895,7 +3908,7 @@ class TestWorkerRunner(LocustTestCase):
|
|
3895
3908
|
)
|
3896
3909
|
|
3897
3910
|
# wait for worker to spawn locusts
|
3898
|
-
self.
|
3911
|
+
self.assertTrue(client.get_messages("spawning"))
|
3899
3912
|
worker.spawning_greenlet.join()
|
3900
3913
|
self.assertEqual(1, len(worker.user_greenlets))
|
3901
3914
|
|
@@ -3942,9 +3955,10 @@ class TestWorkerRunner(LocustTestCase):
|
|
3942
3955
|
with mock.patch("locust.runners.CONNECT_TIMEOUT", new=1):
|
3943
3956
|
with mock.patch("locust.rpc.rpc.Client", mocked_rpc()) as client:
|
3944
3957
|
worker = self.get_runner(environment=Environment(), user_classes=[MyTestUser], client=client)
|
3958
|
+
messages = client.get_messages()
|
3945
3959
|
|
3946
|
-
self.assertEqual("client_ready",
|
3947
|
-
self.assertEqual(1, len(
|
3960
|
+
self.assertEqual("client_ready", messages[0].type)
|
3961
|
+
self.assertEqual(1, len(messages))
|
3948
3962
|
self.assertTrue(worker.connected)
|
3949
3963
|
|
3950
3964
|
def test_worker_connect_failure(self):
|
@@ -3962,6 +3976,71 @@ class TestWorkerRunner(LocustTestCase):
|
|
3962
3976
|
)
|
3963
3977
|
self.assertEqual(2, len(client.outbox))
|
3964
3978
|
|
3979
|
+
def test_send_logs(self):
|
3980
|
+
class MyUser(User):
|
3981
|
+
wait_time = constant(1)
|
3982
|
+
|
3983
|
+
@task
|
3984
|
+
def my_task(self):
|
3985
|
+
pass
|
3986
|
+
|
3987
|
+
with mock.patch("locust.rpc.rpc.Client", mocked_rpc()) as client:
|
3988
|
+
short_time = 0.05
|
3989
|
+
|
3990
|
+
log_handler = LogReader()
|
3991
|
+
log_handler.name = "log_reader"
|
3992
|
+
log_handler.setLevel(logging.INFO)
|
3993
|
+
logger = logging.getLogger("root")
|
3994
|
+
logger.addHandler(log_handler)
|
3995
|
+
log_line = "some log info"
|
3996
|
+
logger.info(log_line)
|
3997
|
+
|
3998
|
+
worker = self.get_runner(environment=Environment(), user_classes=[MyUser], client=client)
|
3999
|
+
|
4000
|
+
gevent.sleep(short_time)
|
4001
|
+
|
4002
|
+
messages = client.get_messages()
|
4003
|
+
|
4004
|
+
self.assertEqual("logs", messages[3].type)
|
4005
|
+
self.assertEqual(log_line, messages[3].data.get("logs", [])[0])
|
4006
|
+
self.assertEqual(worker.client_id, messages[3].data.get("worker_id"))
|
4007
|
+
worker.quit()
|
4008
|
+
|
4009
|
+
def test_quit_worker_logs(self):
|
4010
|
+
class MyUser(User):
|
4011
|
+
wait_time = constant(1)
|
4012
|
+
|
4013
|
+
@task
|
4014
|
+
def my_task(self):
|
4015
|
+
pass
|
4016
|
+
|
4017
|
+
with mock.patch("locust.rpc.rpc.Client", mocked_rpc()) as client:
|
4018
|
+
short_time = 0.05
|
4019
|
+
|
4020
|
+
log_handler = LogReader()
|
4021
|
+
log_handler.name = "log_reader"
|
4022
|
+
log_handler.setLevel(logging.INFO)
|
4023
|
+
logger = logging.getLogger("root")
|
4024
|
+
logger.addHandler(log_handler)
|
4025
|
+
log_line = "spamming log"
|
4026
|
+
|
4027
|
+
for _ in range(11):
|
4028
|
+
logger.info(log_line)
|
4029
|
+
|
4030
|
+
worker = self.get_runner(environment=Environment(), user_classes=[MyUser], client=client)
|
4031
|
+
|
4032
|
+
gevent.sleep(short_time)
|
4033
|
+
|
4034
|
+
message = client.get_messages("logs")[0]
|
4035
|
+
|
4036
|
+
self.assertEqual(
|
4037
|
+
"The worker attempted to send more than 10 log lines in one interval. Further log sending was disabled for this worker.",
|
4038
|
+
message.data.get("logs", [])[-1],
|
4039
|
+
)
|
4040
|
+
self.assertEqual(worker.client_id, message.data.get("worker_id"))
|
4041
|
+
worker.quit()
|
4042
|
+
logger.removeHandler(log_handler)
|
4043
|
+
|
3965
4044
|
|
3966
4045
|
class TestMessageSerializing(unittest.TestCase):
|
3967
4046
|
def test_message_serialize(self):
|
locust/test/test_web.py
CHANGED
@@ -1020,7 +1020,25 @@ class TestWebUI(LocustTestCase, _HeaderCheckMixin):
|
|
1020
1020
|
|
1021
1021
|
response = requests.get("http://127.0.0.1:%i/logs" % self.web_port)
|
1022
1022
|
|
1023
|
-
self.assertIn(log_line, response.json().get("
|
1023
|
+
self.assertIn(log_line, response.json().get("master"))
|
1024
|
+
|
1025
|
+
def test_worker_logs(self):
|
1026
|
+
log_handler = LogReader()
|
1027
|
+
log_handler.name = "log_reader"
|
1028
|
+
log_handler.setLevel(logging.INFO)
|
1029
|
+
logger = logging.getLogger("root")
|
1030
|
+
logger.addHandler(log_handler)
|
1031
|
+
log_line = "some log info"
|
1032
|
+
logger.info(log_line)
|
1033
|
+
|
1034
|
+
worker_id = "123"
|
1035
|
+
worker_log_line = "worker log"
|
1036
|
+
self.environment.update_worker_logs({"worker_id": worker_id, "logs": [worker_log_line]})
|
1037
|
+
|
1038
|
+
response = requests.get("http://127.0.0.1:%i/logs" % self.web_port)
|
1039
|
+
|
1040
|
+
self.assertIn(log_line, response.json().get("master"))
|
1041
|
+
self.assertIn(worker_log_line, response.json().get("workers").get(worker_id))
|
1024
1042
|
|
1025
1043
|
def test_template_args(self):
|
1026
1044
|
class MyUser(User):
|
locust/web.py
CHANGED
@@ -33,7 +33,7 @@ from . import __version__ as version
|
|
33
33
|
from . import argument_parser
|
34
34
|
from . import stats as stats_module
|
35
35
|
from .html import BUILD_PATH, ROOT_PATH, STATIC_PATH, get_html_report
|
36
|
-
from .log import greenlet_exception_logger
|
36
|
+
from .log import get_logs, greenlet_exception_logger
|
37
37
|
from .runners import STATE_MISSING, STATE_RUNNING, MasterRunner
|
38
38
|
from .stats import StatsCSV, StatsCSVFileWriter, StatsErrorDict, sort_stats
|
39
39
|
from .user.inspectuser import get_ratio
|
@@ -490,16 +490,7 @@ class WebUI:
|
|
490
490
|
@app.route("/logs")
|
491
491
|
@self.auth_required_if_enabled
|
492
492
|
def logs():
|
493
|
-
|
494
|
-
handler for handler in logging.getLogger("root").handlers if handler.name == "log_reader"
|
495
|
-
]
|
496
|
-
|
497
|
-
if log_reader_handler:
|
498
|
-
logs = log_reader_handler[0].logs
|
499
|
-
else:
|
500
|
-
logs = []
|
501
|
-
|
502
|
-
return jsonify({"logs": logs})
|
493
|
+
return jsonify({"master": get_logs(), "workers": self.environment.worker_logs})
|
503
494
|
|
504
495
|
@app.route("/login")
|
505
496
|
def login():
|