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.
Files changed (42) hide show
  1. setu_trafficmonitor-2.0.0.dist-info/LICENSE +21 -0
  2. setu_trafficmonitor-2.0.0.dist-info/METADATA +401 -0
  3. setu_trafficmonitor-2.0.0.dist-info/RECORD +42 -0
  4. setu_trafficmonitor-2.0.0.dist-info/WHEEL +5 -0
  5. setu_trafficmonitor-2.0.0.dist-info/top_level.txt +1 -0
  6. trafficmonitor/__init__.py +11 -0
  7. trafficmonitor/admin.py +217 -0
  8. trafficmonitor/analytics/__init__.py +0 -0
  9. trafficmonitor/analytics/enhanced_queries.py +286 -0
  10. trafficmonitor/analytics/serializers.py +238 -0
  11. trafficmonitor/analytics/tests.py +757 -0
  12. trafficmonitor/analytics/urls.py +18 -0
  13. trafficmonitor/analytics/views.py +694 -0
  14. trafficmonitor/apps.py +7 -0
  15. trafficmonitor/circuit_breaker.py +63 -0
  16. trafficmonitor/conf.py +154 -0
  17. trafficmonitor/dashboard_security.py +111 -0
  18. trafficmonitor/db_utils.py +37 -0
  19. trafficmonitor/exceptions.py +93 -0
  20. trafficmonitor/health.py +66 -0
  21. trafficmonitor/load_test.py +423 -0
  22. trafficmonitor/load_test_api.py +307 -0
  23. trafficmonitor/management/__init__.py +1 -0
  24. trafficmonitor/management/commands/__init__.py +1 -0
  25. trafficmonitor/management/commands/cleanup_request_logs.py +77 -0
  26. trafficmonitor/middleware.py +383 -0
  27. trafficmonitor/migrations/0001_initial.py +93 -0
  28. trafficmonitor/migrations/__init__.py +0 -0
  29. trafficmonitor/models.py +206 -0
  30. trafficmonitor/monitoring.py +104 -0
  31. trafficmonitor/permissions.py +64 -0
  32. trafficmonitor/security.py +180 -0
  33. trafficmonitor/settings_production.py +105 -0
  34. trafficmonitor/static/analytics/css/dashboard.css +99 -0
  35. trafficmonitor/static/analytics/js/dashboard-production.js +339 -0
  36. trafficmonitor/static/analytics/js/dashboard-v2.js +697 -0
  37. trafficmonitor/static/analytics/js/dashboard.js +693 -0
  38. trafficmonitor/tasks.py +137 -0
  39. trafficmonitor/templates/analytics/dashboard.html +500 -0
  40. trafficmonitor/tests.py +246 -0
  41. trafficmonitor/views.py +3 -0
  42. 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()