setu-trafficmonitor 2.0.0__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.
- setu_trafficmonitor-2.0.0.dist-info/LICENSE +21 -0
- setu_trafficmonitor-2.0.0.dist-info/METADATA +401 -0
- setu_trafficmonitor-2.0.0.dist-info/RECORD +42 -0
- setu_trafficmonitor-2.0.0.dist-info/WHEEL +5 -0
- setu_trafficmonitor-2.0.0.dist-info/top_level.txt +1 -0
- trafficmonitor/__init__.py +11 -0
- trafficmonitor/admin.py +217 -0
- trafficmonitor/analytics/__init__.py +0 -0
- trafficmonitor/analytics/enhanced_queries.py +286 -0
- trafficmonitor/analytics/serializers.py +238 -0
- trafficmonitor/analytics/tests.py +757 -0
- trafficmonitor/analytics/urls.py +18 -0
- trafficmonitor/analytics/views.py +694 -0
- trafficmonitor/apps.py +7 -0
- trafficmonitor/circuit_breaker.py +63 -0
- trafficmonitor/conf.py +154 -0
- trafficmonitor/dashboard_security.py +111 -0
- trafficmonitor/db_utils.py +37 -0
- trafficmonitor/exceptions.py +93 -0
- trafficmonitor/health.py +66 -0
- trafficmonitor/load_test.py +423 -0
- trafficmonitor/load_test_api.py +307 -0
- trafficmonitor/management/__init__.py +1 -0
- trafficmonitor/management/commands/__init__.py +1 -0
- trafficmonitor/management/commands/cleanup_request_logs.py +77 -0
- trafficmonitor/middleware.py +383 -0
- trafficmonitor/migrations/0001_initial.py +93 -0
- trafficmonitor/migrations/__init__.py +0 -0
- trafficmonitor/models.py +206 -0
- trafficmonitor/monitoring.py +104 -0
- trafficmonitor/permissions.py +64 -0
- trafficmonitor/security.py +180 -0
- trafficmonitor/settings_production.py +105 -0
- trafficmonitor/static/analytics/css/dashboard.css +99 -0
- trafficmonitor/static/analytics/js/dashboard-production.js +339 -0
- trafficmonitor/static/analytics/js/dashboard-v2.js +697 -0
- trafficmonitor/static/analytics/js/dashboard.js +693 -0
- trafficmonitor/tasks.py +137 -0
- trafficmonitor/templates/analytics/dashboard.html +500 -0
- trafficmonitor/tests.py +246 -0
- trafficmonitor/views.py +3 -0
- trafficmonitor/websocket_consumers.py +128 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aggressive Load Testing Script for Analytics Dashboard
|
|
3
|
+
Tests dashboard performance under extreme load with various HTTP methods.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python trafficmonitor/load_test.py --threads 10 --requests 1000
|
|
7
|
+
python trafficmonitor/load_test.py --aggressive --duration 60
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import random
|
|
14
|
+
import string
|
|
15
|
+
import sys
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
from datetime import datetime, timedelta
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from queue import Queue
|
|
21
|
+
|
|
22
|
+
# Add project directory to path
|
|
23
|
+
project_dir = Path(__file__).resolve().parent.parent
|
|
24
|
+
sys.path.insert(0, str(project_dir))
|
|
25
|
+
|
|
26
|
+
import django
|
|
27
|
+
|
|
28
|
+
# Setup Django environment
|
|
29
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'setu.settings')
|
|
30
|
+
django.setup()
|
|
31
|
+
|
|
32
|
+
from django.contrib.auth import get_user_model
|
|
33
|
+
from django.utils import timezone
|
|
34
|
+
from trafficmonitor.models import RequestLog
|
|
35
|
+
|
|
36
|
+
User = get_user_model()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class LoadTestStats:
|
|
40
|
+
"""Thread-safe statistics collector."""
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self.lock = threading.Lock()
|
|
44
|
+
self.total_requests = 0
|
|
45
|
+
self.successful_requests = 0
|
|
46
|
+
self.failed_requests = 0
|
|
47
|
+
self.total_time = 0
|
|
48
|
+
self.errors = []
|
|
49
|
+
self.start_time = None
|
|
50
|
+
self.end_time = None
|
|
51
|
+
self.response_times = []
|
|
52
|
+
|
|
53
|
+
def record_success(self, response_time):
|
|
54
|
+
with self.lock:
|
|
55
|
+
self.total_requests += 1
|
|
56
|
+
self.successful_requests += 1
|
|
57
|
+
self.total_time += response_time
|
|
58
|
+
self.response_times.append(response_time)
|
|
59
|
+
|
|
60
|
+
def record_failure(self, error):
|
|
61
|
+
with self.lock:
|
|
62
|
+
self.total_requests += 1
|
|
63
|
+
self.failed_requests += 1
|
|
64
|
+
self.errors.append(str(error))
|
|
65
|
+
|
|
66
|
+
def get_stats(self):
|
|
67
|
+
with self.lock:
|
|
68
|
+
duration = (self.end_time - self.start_time).total_seconds() if self.end_time and self.start_time else 0
|
|
69
|
+
avg_time = self.total_time / self.successful_requests if self.successful_requests > 0 else 0
|
|
70
|
+
|
|
71
|
+
sorted_times = sorted(self.response_times)
|
|
72
|
+
p50 = sorted_times[len(sorted_times) // 2] if sorted_times else 0
|
|
73
|
+
p95 = sorted_times[int(len(sorted_times) * 0.95)] if sorted_times else 0
|
|
74
|
+
p99 = sorted_times[int(len(sorted_times) * 0.99)] if sorted_times else 0
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
'total_requests': self.total_requests,
|
|
78
|
+
'successful': self.successful_requests,
|
|
79
|
+
'failed': self.failed_requests,
|
|
80
|
+
'duration_seconds': duration,
|
|
81
|
+
'requests_per_second': self.total_requests / duration if duration > 0 else 0,
|
|
82
|
+
'avg_response_time_ms': avg_time * 1000,
|
|
83
|
+
'p50_ms': p50 * 1000,
|
|
84
|
+
'p95_ms': p95 * 1000,
|
|
85
|
+
'p99_ms': p99 * 1000,
|
|
86
|
+
'error_rate': self.failed_requests / self.total_requests * 100 if self.total_requests > 0 else 0,
|
|
87
|
+
'errors': self.errors[:10], # First 10 errors
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class RequestGenerator:
|
|
92
|
+
"""Generates realistic test requests."""
|
|
93
|
+
|
|
94
|
+
METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
|
95
|
+
STATUS_CODES = [200, 201, 400, 401, 403, 404, 500, 502, 503]
|
|
96
|
+
ENDPOINTS = [
|
|
97
|
+
'/api/v1/users/',
|
|
98
|
+
'/api/v1/products/',
|
|
99
|
+
'/api/v1/orders/',
|
|
100
|
+
'/api/v1/monitoring/',
|
|
101
|
+
'/api/v1/operations/',
|
|
102
|
+
'/api/v1/nursery/',
|
|
103
|
+
'/api/v1/reports/',
|
|
104
|
+
'/api/v1/contracts/',
|
|
105
|
+
'/api/v1/finance/',
|
|
106
|
+
'/api/v1/integrations/',
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
def __init__(self, user=None):
|
|
110
|
+
self.user = user
|
|
111
|
+
|
|
112
|
+
def generate_random_request(self):
|
|
113
|
+
"""Generate a random request log entry."""
|
|
114
|
+
method = random.choice(self.METHODS)
|
|
115
|
+
endpoint = random.choice(self.ENDPOINTS)
|
|
116
|
+
status_code = random.choice(self.STATUS_CODES)
|
|
117
|
+
|
|
118
|
+
# Weighted towards success
|
|
119
|
+
if random.random() < 0.7:
|
|
120
|
+
status_code = 200 if method == 'GET' else 201
|
|
121
|
+
|
|
122
|
+
response_time = random.uniform(10, 2000) # 10ms to 2s
|
|
123
|
+
if status_code >= 500:
|
|
124
|
+
response_time = random.uniform(500, 5000) # Errors are slower
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
'method': method,
|
|
128
|
+
'path': endpoint + self._random_id(),
|
|
129
|
+
'full_url': f'http://localhost:8000{endpoint}',
|
|
130
|
+
'status_code': status_code,
|
|
131
|
+
'requested_user_id': str(self.user.pk) if self.user else None,
|
|
132
|
+
'ip_address': self._random_ip(),
|
|
133
|
+
'user_agent': self._random_user_agent(),
|
|
134
|
+
'response_time_ms': response_time,
|
|
135
|
+
'query_count': random.randint(1, 50),
|
|
136
|
+
'content_length': random.randint(100, 10000),
|
|
137
|
+
'timestamp': timezone.now() - timedelta(seconds=random.randint(0, 3600)),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
def _random_id(self):
|
|
141
|
+
"""Generate random ID for URL."""
|
|
142
|
+
return str(random.randint(1, 10000))
|
|
143
|
+
|
|
144
|
+
def _random_ip(self):
|
|
145
|
+
"""Generate random IP address."""
|
|
146
|
+
return f'192.168.{random.randint(1, 255)}.{random.randint(1, 255)}'
|
|
147
|
+
|
|
148
|
+
def _random_user_agent(self):
|
|
149
|
+
"""Generate random user agent."""
|
|
150
|
+
agents = [
|
|
151
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
152
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
153
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
|
|
154
|
+
'PostmanRuntime/7.29.2',
|
|
155
|
+
'python-requests/2.28.1',
|
|
156
|
+
]
|
|
157
|
+
return random.choice(agents)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class LoadTester:
|
|
161
|
+
"""Main load testing coordinator."""
|
|
162
|
+
|
|
163
|
+
def __init__(self, num_threads=10, requests_per_thread=100, duration=None, aggressive=False):
|
|
164
|
+
self.num_threads = num_threads
|
|
165
|
+
self.requests_per_thread = requests_per_thread
|
|
166
|
+
self.duration = duration # Duration in seconds
|
|
167
|
+
self.aggressive = aggressive
|
|
168
|
+
self.stats = LoadTestStats()
|
|
169
|
+
self.stop_flag = threading.Event()
|
|
170
|
+
|
|
171
|
+
# Create test user
|
|
172
|
+
self.user, _ = User.objects.get_or_create(
|
|
173
|
+
username='loadtest_user',
|
|
174
|
+
defaults={'email': 'loadtest@test.com'}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
self.generator = RequestGenerator(user=self.user)
|
|
178
|
+
|
|
179
|
+
def _worker_thread(self, thread_id, task_queue):
|
|
180
|
+
"""Worker thread that processes load testing tasks."""
|
|
181
|
+
while not self.stop_flag.is_set():
|
|
182
|
+
try:
|
|
183
|
+
# Check if we should stop
|
|
184
|
+
if self.duration:
|
|
185
|
+
if (datetime.now() - self.stats.start_time).total_seconds() >= self.duration:
|
|
186
|
+
break
|
|
187
|
+
|
|
188
|
+
# Get task from queue
|
|
189
|
+
try:
|
|
190
|
+
task = task_queue.get(timeout=0.1)
|
|
191
|
+
except:
|
|
192
|
+
if not self.duration:
|
|
193
|
+
break
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
# Execute task
|
|
197
|
+
start = time.time()
|
|
198
|
+
try:
|
|
199
|
+
self._create_request_log()
|
|
200
|
+
elapsed = time.time() - start
|
|
201
|
+
self.stats.record_success(elapsed)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
self.stats.record_failure(e)
|
|
204
|
+
finally:
|
|
205
|
+
if not self.duration:
|
|
206
|
+
task_queue.task_done()
|
|
207
|
+
|
|
208
|
+
# In aggressive mode, don't sleep
|
|
209
|
+
if not self.aggressive:
|
|
210
|
+
time.sleep(random.uniform(0.001, 0.01))
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
print(f"Thread {thread_id} error: {e}")
|
|
214
|
+
|
|
215
|
+
def _create_request_log(self):
|
|
216
|
+
"""Create a request log entry."""
|
|
217
|
+
log_data = self.generator.generate_random_request()
|
|
218
|
+
RequestLog.objects.create(**log_data)
|
|
219
|
+
|
|
220
|
+
def _bulk_create_requests(self, count):
|
|
221
|
+
"""Bulk create request logs for initial data."""
|
|
222
|
+
print(f"Creating {count} initial request logs...")
|
|
223
|
+
|
|
224
|
+
logs = []
|
|
225
|
+
for i in range(count):
|
|
226
|
+
log_data = self.generator.generate_random_request()
|
|
227
|
+
logs.append(RequestLog(**log_data))
|
|
228
|
+
|
|
229
|
+
if len(logs) >= 1000:
|
|
230
|
+
RequestLog.objects.bulk_create(logs, ignore_conflicts=True)
|
|
231
|
+
logs = []
|
|
232
|
+
print(f" Created {i + 1}/{count}...")
|
|
233
|
+
|
|
234
|
+
if logs:
|
|
235
|
+
RequestLog.objects.bulk_create(logs, ignore_conflicts=True)
|
|
236
|
+
|
|
237
|
+
print(f"✓ Created {count} initial logs")
|
|
238
|
+
|
|
239
|
+
def run(self, create_initial_data=0):
|
|
240
|
+
"""Run the load test."""
|
|
241
|
+
print("=" * 70)
|
|
242
|
+
print("ANALYTICS DASHBOARD LOAD TEST")
|
|
243
|
+
print("=" * 70)
|
|
244
|
+
print(f"Threads: {self.num_threads}")
|
|
245
|
+
print(f"Requests per thread: {self.requests_per_thread if not self.duration else 'unlimited'}")
|
|
246
|
+
print(f"Duration: {self.duration}s" if self.duration else "Duration: Until complete")
|
|
247
|
+
print(f"Mode: {'AGGRESSIVE' if self.aggressive else 'NORMAL'}")
|
|
248
|
+
print("=" * 70)
|
|
249
|
+
|
|
250
|
+
# Create initial data if requested
|
|
251
|
+
if create_initial_data > 0:
|
|
252
|
+
self._bulk_create_requests(create_initial_data)
|
|
253
|
+
|
|
254
|
+
# Create task queue
|
|
255
|
+
task_queue = Queue()
|
|
256
|
+
|
|
257
|
+
if not self.duration:
|
|
258
|
+
# Queue-based: Fixed number of requests
|
|
259
|
+
total_requests = self.num_threads * self.requests_per_thread
|
|
260
|
+
for i in range(total_requests):
|
|
261
|
+
task_queue.put(i)
|
|
262
|
+
print(f"\nQueued {total_requests} requests\n")
|
|
263
|
+
else:
|
|
264
|
+
# Time-based: Run for duration
|
|
265
|
+
print(f"\nRunning for {self.duration} seconds\n")
|
|
266
|
+
|
|
267
|
+
# Start timer
|
|
268
|
+
self.stats.start_time = datetime.now()
|
|
269
|
+
|
|
270
|
+
# Create and start worker threads
|
|
271
|
+
threads = []
|
|
272
|
+
for i in range(self.num_threads):
|
|
273
|
+
t = threading.Thread(
|
|
274
|
+
target=self._worker_thread,
|
|
275
|
+
args=(i, task_queue),
|
|
276
|
+
daemon=True
|
|
277
|
+
)
|
|
278
|
+
t.start()
|
|
279
|
+
threads.append(t)
|
|
280
|
+
|
|
281
|
+
# Progress reporter
|
|
282
|
+
def report_progress():
|
|
283
|
+
while not self.stop_flag.is_set():
|
|
284
|
+
time.sleep(5)
|
|
285
|
+
current_stats = self.stats.get_stats()
|
|
286
|
+
print(f"Progress: {current_stats['total_requests']} requests, "
|
|
287
|
+
f"{current_stats['requests_per_second']:.1f} req/s, "
|
|
288
|
+
f"{current_stats['error_rate']:.1f}% errors")
|
|
289
|
+
|
|
290
|
+
reporter = threading.Thread(target=report_progress, daemon=True)
|
|
291
|
+
reporter.start()
|
|
292
|
+
|
|
293
|
+
# Wait for completion
|
|
294
|
+
try:
|
|
295
|
+
if self.duration:
|
|
296
|
+
time.sleep(self.duration)
|
|
297
|
+
self.stop_flag.set()
|
|
298
|
+
else:
|
|
299
|
+
task_queue.join()
|
|
300
|
+
|
|
301
|
+
# Wait for threads
|
|
302
|
+
for t in threads:
|
|
303
|
+
t.join(timeout=5)
|
|
304
|
+
|
|
305
|
+
except KeyboardInterrupt:
|
|
306
|
+
print("\n\nStopping load test...")
|
|
307
|
+
self.stop_flag.set()
|
|
308
|
+
|
|
309
|
+
self.stats.end_time = datetime.now()
|
|
310
|
+
|
|
311
|
+
# Print results
|
|
312
|
+
self._print_results()
|
|
313
|
+
|
|
314
|
+
def _print_results(self):
|
|
315
|
+
"""Print test results."""
|
|
316
|
+
stats = self.stats.get_stats()
|
|
317
|
+
|
|
318
|
+
print("\n" + "=" * 70)
|
|
319
|
+
print("LOAD TEST RESULTS")
|
|
320
|
+
print("=" * 70)
|
|
321
|
+
print(f"Total Requests: {stats['total_requests']:,}")
|
|
322
|
+
print(f"Successful: {stats['successful']:,}")
|
|
323
|
+
print(f"Failed: {stats['failed']:,}")
|
|
324
|
+
print(f"Duration: {stats['duration_seconds']:.2f}s")
|
|
325
|
+
print(f"Requests/sec: {stats['requests_per_second']:.2f}")
|
|
326
|
+
print(f"Error Rate: {stats['error_rate']:.2f}%")
|
|
327
|
+
print("\nResponse Times:")
|
|
328
|
+
print(f" Average: {stats['avg_response_time_ms']:.2f}ms")
|
|
329
|
+
print(f" 50th percentile: {stats['p50_ms']:.2f}ms")
|
|
330
|
+
print(f" 95th percentile: {stats['p95_ms']:.2f}ms")
|
|
331
|
+
print(f" 99th percentile: {stats['p99_ms']:.2f}ms")
|
|
332
|
+
|
|
333
|
+
if stats['errors']:
|
|
334
|
+
print("\nSample Errors:")
|
|
335
|
+
for i, error in enumerate(stats['errors'], 1):
|
|
336
|
+
print(f" {i}. {error}")
|
|
337
|
+
|
|
338
|
+
print("=" * 70)
|
|
339
|
+
|
|
340
|
+
# Database stats
|
|
341
|
+
total_logs = RequestLog.objects.count()
|
|
342
|
+
print(f"\nTotal logs in database: {total_logs:,}")
|
|
343
|
+
|
|
344
|
+
# Test analytics query performance
|
|
345
|
+
print("\nTesting Analytics Dashboard Performance...")
|
|
346
|
+
self._test_dashboard_performance()
|
|
347
|
+
|
|
348
|
+
def _test_dashboard_performance(self):
|
|
349
|
+
"""Test how long analytics queries take."""
|
|
350
|
+
from trafficmonitor.analytics.views import AnalyticsQueryHelper
|
|
351
|
+
|
|
352
|
+
now = timezone.now()
|
|
353
|
+
start = now - timedelta(days=7)
|
|
354
|
+
|
|
355
|
+
tests = [
|
|
356
|
+
("Total Requests", lambda: AnalyticsQueryHelper.get_total_requests(start, now)),
|
|
357
|
+
("Status Codes", lambda: AnalyticsQueryHelper.get_requests_by_status_code(start, now)),
|
|
358
|
+
("Top Endpoints", lambda: AnalyticsQueryHelper.get_top_endpoints(start, now)),
|
|
359
|
+
("Comprehensive Analytics", lambda: AnalyticsQueryHelper.get_comprehensive_analytics(start, now)),
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
print("\nQuery Performance:")
|
|
363
|
+
for test_name, test_func in tests:
|
|
364
|
+
start_time = time.time()
|
|
365
|
+
try:
|
|
366
|
+
result = test_func()
|
|
367
|
+
elapsed = (time.time() - start_time) * 1000
|
|
368
|
+
print(f" {test_name:.<35} {elapsed:>8.2f}ms ✓")
|
|
369
|
+
except Exception as e:
|
|
370
|
+
elapsed = (time.time() - start_time) * 1000
|
|
371
|
+
print(f" {test_name:.<35} {elapsed:>8.2f}ms ✗ ({str(e)[:30]})")
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def main():
|
|
375
|
+
parser = argparse.ArgumentParser(description='Load test the analytics dashboard')
|
|
376
|
+
parser.add_argument('--threads', '-t', type=int, default=10,
|
|
377
|
+
help='Number of concurrent threads (default: 10)')
|
|
378
|
+
parser.add_argument('--requests', '-r', type=int, default=100,
|
|
379
|
+
help='Requests per thread (default: 100)')
|
|
380
|
+
parser.add_argument('--duration', '-d', type=int,
|
|
381
|
+
help='Run for specified seconds (overrides --requests)')
|
|
382
|
+
parser.add_argument('--aggressive', '-a', action='store_true',
|
|
383
|
+
help='Aggressive mode: no delays between requests')
|
|
384
|
+
parser.add_argument('--initial-data', '-i', type=int, default=0,
|
|
385
|
+
help='Create N initial logs before test (default: 0)')
|
|
386
|
+
parser.add_argument('--preset', choices=['light', 'medium', 'heavy', 'extreme'],
|
|
387
|
+
help='Use preset configuration')
|
|
388
|
+
|
|
389
|
+
args = parser.parse_args()
|
|
390
|
+
|
|
391
|
+
# Handle presets
|
|
392
|
+
if args.preset == 'light':
|
|
393
|
+
args.threads = 5
|
|
394
|
+
args.requests = 100
|
|
395
|
+
args.initial_data = 1000
|
|
396
|
+
elif args.preset == 'medium':
|
|
397
|
+
args.threads = 20
|
|
398
|
+
args.requests = 500
|
|
399
|
+
args.initial_data = 10000
|
|
400
|
+
elif args.preset == 'heavy':
|
|
401
|
+
args.threads = 50
|
|
402
|
+
args.requests = 1000
|
|
403
|
+
args.initial_data = 50000
|
|
404
|
+
args.aggressive = True
|
|
405
|
+
elif args.preset == 'extreme':
|
|
406
|
+
args.threads = 100
|
|
407
|
+
args.duration = 60
|
|
408
|
+
args.initial_data = 100000
|
|
409
|
+
args.aggressive = True
|
|
410
|
+
|
|
411
|
+
# Create and run load tester
|
|
412
|
+
tester = LoadTester(
|
|
413
|
+
num_threads=args.threads,
|
|
414
|
+
requests_per_thread=args.requests,
|
|
415
|
+
duration=args.duration,
|
|
416
|
+
aggressive=args.aggressive
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
tester.run(create_initial_data=args.initial_data)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
if __name__ == '__main__':
|
|
423
|
+
main()
|