0
# Events System
1
2
Locust's event system provides extensibility hooks for custom metrics, logging, integrations, and test lifecycle management. The global `events` instance offers access to all built-in events for extending Locust functionality.
3
4
## Capabilities
5
6
### Global Events Instance
7
8
The primary interface for accessing Locust's event system, available as a global instance.
9
10
```python { .api }
11
from locust import events
12
13
# Global events instance providing access to all event hooks
14
events: Events
15
```
16
17
### Core Request Events
18
19
Events fired during HTTP request lifecycle for statistics collection and custom processing.
20
21
```python { .api }
22
# Request completion event
23
events.request.add_listener(handler)
24
25
def request_handler(request_type, name, response_time, response_length, exception=None, **kwargs):
26
"""
27
Handle request completion events.
28
29
Args:
30
request_type (str): HTTP method or custom request type
31
name (str): Request name for statistics grouping
32
response_time (float): Response time in milliseconds
33
response_length (int): Response body length in bytes
34
exception (Exception): Exception if request failed
35
**kwargs: Additional request metadata
36
"""
37
38
# User error event
39
events.user_error.add_listener(handler)
40
41
def user_error_handler(user_instance, exception, tb, **kwargs):
42
"""
43
Handle user execution errors.
44
45
Args:
46
user_instance: User instance that encountered error
47
exception (Exception): Exception that occurred
48
tb: Traceback object
49
**kwargs: Additional error context
50
"""
51
```
52
53
### Test Lifecycle Events
54
55
Events for hooking into test execution phases and managing test lifecycle.
56
57
```python { .api }
58
# Test initialization
59
events.init.add_listener(handler)
60
61
def init_handler(environment, **kwargs):
62
"""
63
Handle test initialization.
64
65
Args:
66
environment: Locust environment instance
67
**kwargs: Additional initialization context
68
"""
69
70
# Test start
71
events.test_start.add_listener(handler)
72
73
def test_start_handler(environment, **kwargs):
74
"""
75
Handle test start event.
76
77
Args:
78
environment: Locust environment instance
79
**kwargs: Additional test start context
80
"""
81
82
# Test stopping (before cleanup)
83
events.test_stopping.add_listener(handler)
84
85
def test_stopping_handler(environment, **kwargs):
86
"""
87
Handle test stopping event (before cleanup).
88
89
Args:
90
environment: Locust environment instance
91
**kwargs: Additional context
92
"""
93
94
# Test stop (after cleanup)
95
events.test_stop.add_listener(handler)
96
97
def test_stop_handler(environment, **kwargs):
98
"""
99
Handle test stop event (after cleanup).
100
101
Args:
102
environment: Locust environment instance
103
**kwargs: Additional context
104
"""
105
106
# Test quit (final cleanup)
107
events.quit.add_listener(handler)
108
events.quitting.add_listener(handler)
109
110
def quit_handler(exit_code, **kwargs):
111
"""
112
Handle test quit events.
113
114
Args:
115
exit_code (int): Exit code for test run
116
**kwargs: Additional quit context
117
"""
118
```
119
120
### User Lifecycle Events
121
122
Events related to user spawning and management during test execution.
123
124
```python { .api }
125
# User spawning complete
126
events.spawning_complete.add_listener(handler)
127
128
def spawning_complete_handler(user_count, **kwargs):
129
"""
130
Handle user spawning completion.
131
132
Args:
133
user_count (int): Total number of spawned users
134
**kwargs: Additional spawning context
135
"""
136
```
137
138
### Statistics Events
139
140
Events for custom statistics handling and processing.
141
142
```python { .api }
143
# Statistics reset
144
events.reset_stats.add_listener(handler)
145
146
def reset_stats_handler(**kwargs):
147
"""
148
Handle statistics reset event.
149
150
Args:
151
**kwargs: Reset context
152
"""
153
```
154
155
### Distributed Testing Events
156
157
Events for master/worker coordination in distributed testing scenarios.
158
159
```python { .api }
160
# Worker reporting to master
161
events.report_to_master.add_listener(handler)
162
163
def report_to_master_handler(client_id, data, **kwargs):
164
"""
165
Handle worker report to master.
166
167
Args:
168
client_id (str): Worker client identifier
169
data (dict): Report data from worker
170
**kwargs: Additional report context
171
"""
172
173
# Master receiving worker report
174
events.worker_report.add_listener(handler)
175
176
def worker_report_handler(client_id, data, **kwargs):
177
"""
178
Handle worker report on master.
179
180
Args:
181
client_id (str): Worker client identifier
182
data (dict): Report data from worker
183
**kwargs: Additional report context
184
"""
185
186
# Worker connect to master
187
events.worker_connect.add_listener(handler)
188
189
def worker_connect_handler(client_id, **kwargs):
190
"""
191
Handle worker connection to master.
192
193
Args:
194
client_id (str): Worker client identifier
195
**kwargs: Connection context
196
"""
197
```
198
199
### System Monitoring Events
200
201
Events for system resource monitoring and alerts.
202
203
```python { .api }
204
# CPU usage warning
205
events.cpu_warning.add_listener(handler)
206
207
def cpu_warning_handler(usage_percent, **kwargs):
208
"""
209
Handle CPU usage warnings.
210
211
Args:
212
usage_percent (float): CPU usage percentage
213
**kwargs: Additional system context
214
"""
215
216
# Heartbeat events (distributed testing)
217
events.heartbeat_sent.add_listener(handler)
218
events.heartbeat_received.add_listener(handler)
219
220
def heartbeat_handler(client_id, **kwargs):
221
"""
222
Handle heartbeat events.
223
224
Args:
225
client_id (str): Client identifier
226
**kwargs: Heartbeat context
227
"""
228
229
# Usage monitoring
230
events.usage_monitor.add_listener(handler)
231
232
def usage_monitor_handler(stats, **kwargs):
233
"""
234
Handle usage monitoring events.
235
236
Args:
237
stats (dict): Usage statistics
238
**kwargs: Monitoring context
239
"""
240
```
241
242
### Command Line Events
243
244
Events for customizing command line argument parsing.
245
246
```python { .api }
247
# Command line parser initialization
248
events.init_command_line_parser.add_listener(handler)
249
250
def init_parser_handler(parser, **kwargs):
251
"""
252
Handle command line parser initialization.
253
254
Args:
255
parser: ArgumentParser instance
256
**kwargs: Parser initialization context
257
"""
258
```
259
260
## Event Classes
261
262
### Events Class
263
264
Main events manager class for creating custom event systems.
265
266
```python { .api }
267
class Events:
268
"""
269
Event system manager providing event hooks.
270
271
Attributes:
272
request: Request completion event hook
273
user_error: User error event hook
274
report_to_master: Worker to master reporting hook
275
worker_report: Master receiving worker report hook
276
worker_connect: Worker connection hook
277
spawning_complete: User spawning completion hook
278
quitting: Test quitting hook
279
quit: Test quit hook
280
init: Test initialization hook
281
init_command_line_parser: CLI parser init hook
282
test_start: Test start hook
283
test_stopping: Test stopping hook
284
test_stop: Test stop hook
285
reset_stats: Statistics reset hook
286
cpu_warning: CPU usage warning hook
287
heartbeat_sent: Heartbeat sent hook
288
heartbeat_received: Heartbeat received hook
289
usage_monitor: Usage monitoring hook
290
"""
291
```
292
293
### EventHook Class
294
295
Individual event hook implementation for subscribing and firing events.
296
297
```python { .api }
298
class EventHook:
299
"""
300
Individual event hook for managing event listeners.
301
"""
302
303
def add_listener(self, func):
304
"""
305
Add event listener function.
306
307
Args:
308
func (callable): Event handler function
309
"""
310
311
def remove_listener(self, func):
312
"""
313
Remove event listener function.
314
315
Args:
316
func (callable): Event handler function to remove
317
"""
318
319
def fire(self, **kwargs):
320
"""
321
Fire event to all registered listeners.
322
323
Args:
324
**kwargs: Event data to pass to listeners
325
"""
326
```
327
328
## Usage Examples
329
330
### Custom Metrics Collection
331
332
```python
333
from locust import HttpUser, task, between, events
334
import time
335
import json
336
337
# Custom metrics storage
338
custom_metrics = {
339
"api_calls": 0,
340
"error_count": 0,
341
"response_times": []
342
}
343
344
def on_request(request_type, name, response_time, response_length, exception, **kwargs):
345
"""Collect custom metrics on each request"""
346
custom_metrics["api_calls"] += 1
347
custom_metrics["response_times"].append(response_time)
348
349
if exception:
350
custom_metrics["error_count"] += 1
351
352
# Log slow requests
353
if response_time > 5000: # 5 seconds
354
print(f"SLOW REQUEST: {name} took {response_time}ms")
355
356
def on_test_stop(environment, **kwargs):
357
"""Report custom metrics at test end"""
358
total_calls = custom_metrics["api_calls"]
359
avg_response_time = sum(custom_metrics["response_times"]) / len(custom_metrics["response_times"])
360
error_rate = custom_metrics["error_count"] / total_calls * 100
361
362
print(f"\n--- Custom Metrics ---")
363
print(f"Total API calls: {total_calls}")
364
print(f"Average response time: {avg_response_time:.2f}ms")
365
print(f"Error rate: {error_rate:.2f}%")
366
367
# Save to file
368
with open("custom_metrics.json", "w") as f:
369
json.dump(custom_metrics, f, indent=2)
370
371
# Register event listeners
372
events.request.add_listener(on_request)
373
events.test_stop.add_listener(on_test_stop)
374
375
class APIUser(HttpUser):
376
wait_time = between(1, 3)
377
378
@task
379
def api_call(self):
380
self.client.get("/api/data")
381
```
382
383
### External System Integration
384
385
```python
386
from locust import HttpUser, task, events
387
import requests
388
import json
389
390
class ExternalReporter:
391
def __init__(self, webhook_url):
392
self.webhook_url = webhook_url
393
self.test_start_time = None
394
self.request_count = 0
395
396
def on_test_start(self, environment, **kwargs):
397
"""Notify external system of test start"""
398
self.test_start_time = time.time()
399
payload = {
400
"event": "test_start",
401
"timestamp": self.test_start_time,
402
"users": environment.runner.target_user_count if environment.runner else 0
403
}
404
self.send_webhook(payload)
405
406
def on_request(self, request_type, name, response_time, response_length, exception, **kwargs):
407
"""Track request metrics"""
408
self.request_count += 1
409
410
# Send alerts for errors
411
if exception:
412
payload = {
413
"event": "error",
414
"timestamp": time.time(),
415
"request_type": request_type,
416
"name": name,
417
"error": str(exception)
418
}
419
self.send_webhook(payload)
420
421
def on_test_stop(self, environment, **kwargs):
422
"""Send final test report"""
423
duration = time.time() - self.test_start_time
424
payload = {
425
"event": "test_complete",
426
"timestamp": time.time(),
427
"duration": duration,
428
"total_requests": self.request_count,
429
"stats": environment.stats.serialize_stats() if environment.stats else {}
430
}
431
self.send_webhook(payload)
432
433
def send_webhook(self, payload):
434
"""Send webhook notification"""
435
try:
436
requests.post(self.webhook_url, json=payload, timeout=5)
437
except Exception as e:
438
print(f"Webhook failed: {e}")
439
440
# Initialize external reporter
441
reporter = ExternalReporter("https://monitoring.example.com/webhook")
442
443
# Register event listeners
444
events.test_start.add_listener(reporter.on_test_start)
445
events.request.add_listener(reporter.on_request)
446
events.test_stop.add_listener(reporter.on_test_stop)
447
448
class WebUser(HttpUser):
449
wait_time = between(1, 2)
450
451
@task
452
def browse(self):
453
self.client.get("/")
454
```
455
456
### Custom Statistics and Reporting
457
458
```python
459
from locust import HttpUser, task, events
460
import csv
461
import time
462
from collections import defaultdict
463
464
class DetailedStats:
465
def __init__(self):
466
self.start_time = time.time()
467
self.requests_by_minute = defaultdict(list)
468
self.errors_by_type = defaultdict(int)
469
self.response_time_buckets = defaultdict(int)
470
471
def on_request(self, request_type, name, response_time, response_length, exception, **kwargs):
472
"""Collect detailed request statistics"""
473
minute = int((time.time() - self.start_time) // 60)
474
475
# Track requests per minute
476
self.requests_by_minute[minute].append({
477
"type": request_type,
478
"name": name,
479
"response_time": response_time,
480
"length": response_length,
481
"success": exception is None
482
})
483
484
# Track errors by type
485
if exception:
486
error_type = type(exception).__name__
487
self.errors_by_type[error_type] += 1
488
489
# Response time buckets
490
if response_time < 100:
491
self.response_time_buckets["<100ms"] += 1
492
elif response_time < 500:
493
self.response_time_buckets["100-500ms"] += 1
494
elif response_time < 1000:
495
self.response_time_buckets["500ms-1s"] += 1
496
elif response_time < 5000:
497
self.response_time_buckets["1-5s"] += 1
498
else:
499
self.response_time_buckets[">5s"] += 1
500
501
def on_test_stop(self, environment, **kwargs):
502
"""Generate detailed CSV report"""
503
with open("detailed_stats.csv", "w", newline="") as csvfile:
504
writer = csv.writer(csvfile)
505
writer.writerow(["Minute", "Request_Type", "Name", "Response_Time", "Length", "Success"])
506
507
for minute, requests in self.requests_by_minute.items():
508
for req in requests:
509
writer.writerow([
510
minute, req["type"], req["name"],
511
req["response_time"], req["length"], req["success"]
512
])
513
514
# Print summary
515
print("\n--- Response Time Distribution ---")
516
for bucket, count in self.response_time_buckets.items():
517
print(f"{bucket}: {count} requests")
518
519
print("\n--- Error Summary ---")
520
for error_type, count in self.errors_by_type.items():
521
print(f"{error_type}: {count} occurrences")
522
523
# Initialize detailed stats collector
524
stats_collector = DetailedStats()
525
events.request.add_listener(stats_collector.on_request)
526
events.test_stop.add_listener(stats_collector.on_test_stop)
527
528
class AnalyticsUser(HttpUser):
529
wait_time = between(0.5, 2)
530
531
@task(3)
532
def page_view(self):
533
self.client.get("/page")
534
535
@task(1)
536
def api_call(self):
537
self.client.get("/api/data")
538
```
539
540
### Environment-Specific Event Handling
541
542
```python
543
from locust import HttpUser, task, events
544
import os
545
546
def setup_environment_specific_handlers():
547
"""Setup different event handlers based on environment"""
548
environment = os.getenv("TEST_ENV", "development")
549
550
if environment == "production":
551
# Production: minimal logging, external monitoring
552
def prod_error_handler(user_instance, exception, tb, **kwargs):
553
# Send to external monitoring service
554
send_to_monitoring_service(str(exception))
555
556
events.user_error.add_listener(prod_error_handler)
557
558
elif environment == "staging":
559
# Staging: detailed logging for debugging
560
def staging_request_handler(request_type, name, response_time, response_length, exception, **kwargs):
561
if exception or response_time > 2000:
562
print(f"STAGING ALERT: {name} - {response_time}ms - {exception}")
563
564
events.request.add_listener(staging_request_handler)
565
566
else:
567
# Development: verbose logging
568
def dev_request_handler(**kwargs):
569
print(f"DEV REQUEST: {kwargs}")
570
571
events.request.add_listener(dev_request_handler)
572
573
# Setup environment-specific handling
574
setup_environment_specific_handlers()
575
576
class EnvironmentUser(HttpUser):
577
wait_time = between(1, 3)
578
579
@task
580
def environment_test(self):
581
self.client.get("/api/environment")
582
```
583
584
## Types
585
586
```python { .api }
587
from typing import Callable, Any, Dict, Optional
588
from locust.env import Environment
589
590
# Event handler function types
591
RequestHandler = Callable[[str, str, float, int, Optional[Exception]], None]
592
UserErrorHandler = Callable[[Any, Exception, Any], None]
593
LifecycleHandler = Callable[[Environment], None]
594
StatisticsHandler = Callable[[Dict[str, Any]], None]
595
SystemHandler = Callable[[float], None] # For CPU warnings, etc.
596
597
# Generic event handler
598
EventHandler = Callable[..., None]
599
600
# Event hook interface
601
class EventHook:
602
def add_listener(self, func: EventHandler) -> None: ...
603
def remove_listener(self, func: EventHandler) -> None: ...
604
def fire(self, **kwargs: Any) -> None: ...
605
```