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,217 @@
1
+ from django.contrib import admin
2
+ from django.utils.html import format_html
3
+ try:
4
+ from import_export.admin import ImportExportModelAdmin
5
+ BaseAdmin = ImportExportModelAdmin
6
+ except Exception: # import-export is optional
7
+ BaseAdmin = admin.ModelAdmin
8
+
9
+ from trafficmonitor.models import RequestLog
10
+
11
+
12
+ @admin.register(RequestLog)
13
+ class RequestLogAdmin(BaseAdmin):
14
+ """
15
+ Admin interface for viewing and managing request logs.
16
+ """
17
+ list_display = [
18
+ 'timestamp',
19
+ 'method_colored',
20
+ 'path_truncated',
21
+ 'status_code_colored',
22
+ 'user_display',
23
+ 'ip_address',
24
+ 'response_time_display',
25
+ 'query_count',
26
+ ]
27
+
28
+ list_filter = [
29
+ 'method',
30
+ 'status_code',
31
+ 'timestamp',
32
+ 'requested_user_id',
33
+ ]
34
+
35
+ search_fields = [
36
+ 'path',
37
+ 'full_url',
38
+ 'ip_address',
39
+ 'requested_user_id',
40
+ 'exception',
41
+ ]
42
+
43
+ fields = [
44
+ 'id',
45
+ 'timestamp',
46
+ 'method',
47
+ 'path',
48
+ 'full_url',
49
+ 'status_code',
50
+ 'requested_user_id',
51
+ 'ip_address',
52
+ 'user_agent',
53
+ 'request_headers_formatted',
54
+ 'request_body_formatted',
55
+ 'response_body_formatted',
56
+ 'response_time_ms',
57
+ 'query_count',
58
+ 'exception_formatted',
59
+ 'content_length',
60
+ ]
61
+
62
+ readonly_fields = [
63
+ 'id',
64
+ 'method',
65
+ 'path',
66
+ 'full_url',
67
+ 'status_code',
68
+ 'requested_user_id',
69
+ 'ip_address',
70
+ 'user_agent',
71
+ 'request_headers_formatted',
72
+ 'request_body_formatted',
73
+ 'response_body_formatted',
74
+ 'response_time_ms',
75
+ 'query_count',
76
+ 'exception_formatted',
77
+ 'content_length',
78
+ 'timestamp',
79
+ ]
80
+
81
+ date_hierarchy = 'timestamp'
82
+
83
+ ordering = ['-timestamp']
84
+
85
+ # Disable add and edit permissions - this is a read-only log
86
+ def has_add_permission(self, request):
87
+ return False
88
+
89
+ def has_change_permission(self, request, obj=None):
90
+ # Allow view permission but not edit
91
+ if obj is None:
92
+ return True
93
+ return False
94
+
95
+ def has_delete_permission(self, request, obj=None):
96
+ # Allow deletion through admin
97
+ return True
98
+
99
+ def method_colored(self, obj):
100
+ """Display HTTP method with color coding."""
101
+ colors = {
102
+ 'GET': '#28a745', # Green
103
+ 'POST': '#007bff', # Blue
104
+ 'PUT': '#ffc107', # Yellow
105
+ 'PATCH': '#fd7e14', # Orange
106
+ 'DELETE': '#dc3545', # Red
107
+ }
108
+ color = colors.get(obj.method, '#6c757d')
109
+ return format_html(
110
+ '<span style="color: {}; font-weight: bold;">{}</span>',
111
+ color,
112
+ obj.method
113
+ )
114
+ method_colored.short_description = 'Method'
115
+
116
+ def status_code_colored(self, obj):
117
+ """Display status code with color coding."""
118
+ if obj.status_code < 300:
119
+ color = '#28a745' # Green for success
120
+ elif obj.status_code < 400:
121
+ color = '#17a2b8' # Teal for redirects
122
+ elif obj.status_code < 500:
123
+ color = '#ffc107' # Yellow for client errors
124
+ else:
125
+ color = '#dc3545' # Red for server errors
126
+
127
+ return format_html(
128
+ '<span style="color: {}; font-weight: bold;">{}</span>',
129
+ color,
130
+ obj.status_code
131
+ )
132
+ status_code_colored.short_description = 'Status'
133
+
134
+ def path_truncated(self, obj):
135
+ """Display truncated path for readability."""
136
+ max_length = 50
137
+ if len(obj.path) > max_length:
138
+ return obj.path[:max_length] + '...'
139
+ return obj.path
140
+ path_truncated.short_description = 'Path'
141
+
142
+ def user_display(self, obj):
143
+ """Display user information."""
144
+ if obj.requested_user_id:
145
+ return f"{obj.requested_user_id}"
146
+ return "Anonymous"
147
+ user_display.short_description = 'User'
148
+
149
+ def response_time_display(self, obj):
150
+ """Display response time with color coding."""
151
+ if obj.response_time_ms is None:
152
+ return '-'
153
+
154
+ # Color code based on response time
155
+ if obj.response_time_ms < 100:
156
+ color = '#28a745' # Green - fast
157
+ elif obj.response_time_ms < 500:
158
+ color = '#ffc107' # Yellow - medium
159
+ elif obj.response_time_ms < 1000:
160
+ color = '#fd7e14' # Orange - slow
161
+ else:
162
+ color = '#dc3545' # Red - very slow
163
+
164
+ # Format the time value first, then pass to format_html
165
+ time_str = f"{obj.response_time_ms:.2f} ms"
166
+ return format_html(
167
+ '<span style="color: {};">{}</span>',
168
+ color,
169
+ time_str
170
+ )
171
+ response_time_display.short_description = 'Response Time'
172
+
173
+ def request_headers_formatted(self, obj):
174
+ """Display formatted request headers."""
175
+ if obj.request_headers:
176
+ import json
177
+ return format_html(
178
+ '<pre style="max-height: 300px; overflow: auto;">{}</pre>',
179
+ json.dumps(obj.request_headers, indent=2)
180
+ )
181
+ return '-'
182
+ request_headers_formatted.short_description = 'Request Headers'
183
+
184
+ def request_body_formatted(self, obj):
185
+ """Display formatted request body."""
186
+ if obj.request_body:
187
+ return format_html(
188
+ '<pre style="max-height: 400px; overflow: auto;">{}</pre>',
189
+ obj.request_body
190
+ )
191
+ return '-'
192
+ request_body_formatted.short_description = 'Request Body'
193
+
194
+ def response_body_formatted(self, obj):
195
+ """Display formatted response body."""
196
+ if obj.response_body:
197
+ return format_html(
198
+ '<pre style="max-height: 400px; overflow: auto;">{}</pre>',
199
+ obj.response_body
200
+ )
201
+ return '-'
202
+ response_body_formatted.short_description = 'Response Body'
203
+
204
+ def exception_formatted(self, obj):
205
+ """Display formatted exception traceback."""
206
+ if obj.exception:
207
+ return format_html(
208
+ '<pre style="color: #dc3545; max-height: 500px; overflow: auto;">{}</pre>',
209
+ obj.exception
210
+ )
211
+ return '-'
212
+ exception_formatted.short_description = 'Exception Traceback'
213
+
214
+ def get_queryset(self, request):
215
+ """Optimize queryset."""
216
+ qs = super().get_queryset(request)
217
+ return qs
File without changes
@@ -0,0 +1,286 @@
1
+ """
2
+ Enhanced Analytics Queries for Enterprise-Level Insights
3
+ Focus on Read/Write operations, API performance, and actionable metrics.
4
+ """
5
+ from datetime import timedelta
6
+ from django.db.models import Count, Avg, Max, Min, Q, F, FloatField, ExpressionWrapper
7
+ from django.db.models.functions import TruncDate, TruncHour, TruncDay
8
+ from django.utils import timezone
9
+ from trafficmonitor.models import RequestLog
10
+
11
+
12
+ class EnhancedAnalyticsQueries:
13
+ """
14
+ Enterprise-grade analytics queries focused on actionable insights.
15
+ """
16
+
17
+ @staticmethod
18
+ def get_read_write_summary(start_date, end_date, **filters):
19
+ """
20
+ Get summary of Read vs Write operations.
21
+ Essential for understanding application usage patterns.
22
+
23
+ Returns:
24
+ dict: {
25
+ 'read_count': int,
26
+ 'write_count': int,
27
+ 'delete_count': int,
28
+ 'read_percentage': float,
29
+ 'write_percentage': float,
30
+ 'avg_read_time_ms': float,
31
+ 'avg_write_time_ms': float
32
+ }
33
+ """
34
+ from trafficmonitor.analytics.views import AnalyticsQueryHelper
35
+ qs = AnalyticsQueryHelper.get_base_queryset(start_date, end_date, **filters)
36
+
37
+ summary = qs.aggregate(
38
+ read_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_READ)),
39
+ write_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_WRITE)),
40
+ delete_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_DELETE)),
41
+ total_count=Count('id'),
42
+ avg_read_time=Avg('response_time_ms', filter=Q(
43
+ operation_type=RequestLog.OPERATION_READ,
44
+ response_time_ms__isnull=False
45
+ )),
46
+ avg_write_time=Avg('response_time_ms', filter=Q(
47
+ operation_type=RequestLog.OPERATION_WRITE,
48
+ response_time_ms__isnull=False
49
+ )),
50
+ avg_delete_time=Avg('response_time_ms', filter=Q(
51
+ operation_type=RequestLog.OPERATION_DELETE,
52
+ response_time_ms__isnull=False
53
+ )),
54
+ )
55
+
56
+ # Calculate percentages
57
+ total = summary['total_count'] or 1
58
+ summary['read_percentage'] = (summary['read_count'] / total) * 100
59
+ summary['write_percentage'] = (summary['write_count'] / total) * 100
60
+ summary['delete_percentage'] = (summary['delete_count'] / total) * 100
61
+
62
+ return summary
63
+
64
+ @staticmethod
65
+ def get_read_write_over_time(start_date, end_date, granularity='day', **filters):
66
+ """
67
+ Get Read vs Write operations over time.
68
+ Useful for identifying usage patterns and peak times.
69
+
70
+ Returns:
71
+ List of dicts with period, read_count, write_count
72
+ """
73
+ from trafficmonitor.analytics.views import AnalyticsQueryHelper
74
+ qs = AnalyticsQueryHelper.get_base_queryset(start_date, end_date, **filters)
75
+
76
+ trunc_func = TruncHour if granularity == 'hour' else TruncDay
77
+
78
+ results = qs.annotate(
79
+ period=trunc_func('timestamp')
80
+ ).values('period').annotate(
81
+ read_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_READ)),
82
+ write_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_WRITE)),
83
+ delete_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_DELETE)),
84
+ total_count=Count('id')
85
+ ).order_by('period')
86
+
87
+ return list(results)
88
+
89
+ @staticmethod
90
+ def get_api_health_metrics(start_date, end_date, **filters):
91
+ """
92
+ Get API health metrics including success rates and error rates.
93
+ Critical for SRE/DevOps monitoring.
94
+
95
+ Returns:
96
+ dict with success_rate, error_rate, p50, p95, p99 response times
97
+ """
98
+ from trafficmonitor.analytics.views import AnalyticsQueryHelper
99
+ qs = AnalyticsQueryHelper.get_base_queryset(start_date, end_date, **filters)
100
+ qs = qs.filter(is_api_request=True)
101
+
102
+ total = qs.count()
103
+ if total == 0:
104
+ return {
105
+ 'total_requests': 0,
106
+ 'success_rate': 0,
107
+ 'error_rate': 0,
108
+ 'p50_response_time': 0,
109
+ 'p95_response_time': 0,
110
+ 'p99_response_time': 0,
111
+ }
112
+
113
+ # Success and error rates
114
+ success_count = qs.filter(status_code__lt=400).count()
115
+ error_count = qs.filter(status_code__gte=400).count()
116
+
117
+ # Response time percentiles (approximation using aggregates)
118
+ response_times = qs.filter(response_time_ms__isnull=False).values_list(
119
+ 'response_time_ms', flat=True
120
+ ).order_by('response_time_ms')
121
+
122
+ response_times_list = list(response_times)
123
+ count = len(response_times_list)
124
+
125
+ p50 = response_times_list[int(count * 0.50)] if count > 0 else 0
126
+ p95 = response_times_list[int(count * 0.95)] if count > 0 else 0
127
+ p99 = response_times_list[int(count * 0.99)] if count > 0 else 0
128
+
129
+ return {
130
+ 'total_requests': total,
131
+ 'success_count': success_count,
132
+ 'error_count': error_count,
133
+ 'success_rate': (success_count / total) * 100,
134
+ 'error_rate': (error_count / total) * 100,
135
+ 'p50_response_time': round(p50, 2),
136
+ 'p95_response_time': round(p95, 2),
137
+ 'p99_response_time': round(p99, 2),
138
+ }
139
+
140
+ @staticmethod
141
+ def get_endpoint_category_breakdown(start_date, end_date, limit=10, **filters):
142
+ """
143
+ Get request breakdown by endpoint category.
144
+ Helps identify which microservices/modules are most used.
145
+
146
+ Returns:
147
+ List of dicts with category, count, avg_response_time, error_rate
148
+ """
149
+ from trafficmonitor.analytics.views import AnalyticsQueryHelper
150
+ qs = AnalyticsQueryHelper.get_base_queryset(start_date, end_date, **filters)
151
+ qs = qs.filter(endpoint_category__isnull=False)
152
+
153
+ results = qs.values('endpoint_category').annotate(
154
+ total_count=Count('id'),
155
+ success_count=Count('id', filter=Q(status_code__lt=400)),
156
+ error_count=Count('id', filter=Q(status_code__gte=400)),
157
+ avg_response_time=Avg('response_time_ms', filter=Q(response_time_ms__isnull=False)),
158
+ max_response_time=Max('response_time_ms'),
159
+ ).order_by('-total_count')[:limit]
160
+
161
+ # Add error_rate calculation
162
+ for item in results:
163
+ total = item['total_count'] or 1
164
+ item['error_rate'] = (item['error_count'] / total) * 100
165
+ item['success_rate'] = (item['success_count'] / total) * 100
166
+
167
+ return list(results)
168
+
169
+ @staticmethod
170
+ def get_user_activity_detailed(start_date, end_date, limit=20, **filters):
171
+ """
172
+ Get detailed user activity with Read/Write breakdown.
173
+
174
+ Returns:
175
+ List of dicts with user_id, total_requests, read_count, write_count
176
+ """
177
+ from trafficmonitor.analytics.views import AnalyticsQueryHelper
178
+ qs = AnalyticsQueryHelper.get_base_queryset(start_date, end_date, **filters)
179
+ qs = qs.filter(requested_user_id__isnull=False)
180
+
181
+ results = qs.values('requested_user_id').annotate(
182
+ total_count=Count('id'),
183
+ read_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_READ)),
184
+ write_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_WRITE)),
185
+ delete_count=Count('id', filter=Q(operation_type=RequestLog.OPERATION_DELETE)),
186
+ avg_response_time=Avg('response_time_ms', filter=Q(response_time_ms__isnull=False)),
187
+ error_count=Count('id', filter=Q(status_code__gte=400)),
188
+ ).order_by('-total_count')[:limit]
189
+
190
+ # Add percentages
191
+ for item in results:
192
+ total = item['total_count'] or 1
193
+ item['read_percentage'] = (item['read_count'] / total) * 100
194
+ item['write_percentage'] = (item['write_count'] / total) * 100
195
+ item['error_rate'] = (item['error_count'] / total) * 100
196
+
197
+ return list(results)
198
+
199
+ @staticmethod
200
+ def get_performance_outliers(start_date, end_date, threshold_percentile=95, limit=10, **filters):
201
+ """
202
+ Identify endpoints with performance issues (beyond 95th percentile).
203
+ Critical for performance optimization.
204
+
205
+ Returns:
206
+ List of endpoints exceeding performance threshold
207
+ """
208
+ from trafficmonitor.analytics.views import AnalyticsQueryHelper
209
+ qs = AnalyticsQueryHelper.get_base_queryset(start_date, end_date, **filters)
210
+ qs = qs.filter(response_time_ms__isnull=False)
211
+
212
+ # Calculate 95th percentile threshold
213
+ response_times = list(qs.values_list('response_time_ms', flat=True).order_by('response_time_ms'))
214
+ if not response_times:
215
+ return []
216
+
217
+ threshold = response_times[int(len(response_times) * (threshold_percentile / 100))]
218
+
219
+ # Find endpoints exceeding threshold
220
+ outliers = qs.filter(
221
+ response_time_ms__gte=threshold
222
+ ).values('path', 'operation_type').annotate(
223
+ count=Count('id'),
224
+ avg_response_time=Avg('response_time_ms'),
225
+ max_response_time=Max('response_time_ms'),
226
+ min_response_time=Min('response_time_ms'),
227
+ ).order_by('-avg_response_time')[:limit]
228
+
229
+ return list(outliers)
230
+
231
+ @staticmethod
232
+ def get_error_analysis_by_category(start_date, end_date, **filters):
233
+ """
234
+ Analyze errors by endpoint category and status code.
235
+ Helps identify problematic services.
236
+
237
+ Returns:
238
+ List of dicts with category, status_code, count
239
+ """
240
+ from trafficmonitor.analytics.views import AnalyticsQueryHelper
241
+ qs = AnalyticsQueryHelper.get_base_queryset(start_date, end_date, **filters)
242
+ qs = qs.filter(status_code__gte=400, endpoint_category__isnull=False)
243
+
244
+ results = qs.values('endpoint_category', 'status_code').annotate(
245
+ count=Count('id'),
246
+ example_path=Max('path') # Show an example path
247
+ ).order_by('endpoint_category', '-count')
248
+
249
+ return list(results)
250
+
251
+ @staticmethod
252
+ def get_throughput_metrics(start_date, end_date, **filters):
253
+ """
254
+ Calculate throughput metrics (requests per second/minute).
255
+
256
+ Returns:
257
+ dict with avg_rps, peak_rps, total_requests
258
+ """
259
+ from trafficmonitor.analytics.views import AnalyticsQueryHelper
260
+ qs = AnalyticsQueryHelper.get_base_queryset(start_date, end_date, **filters)
261
+
262
+ total_requests = qs.count()
263
+ time_diff_seconds = (end_date - start_date).total_seconds()
264
+
265
+ if time_diff_seconds == 0:
266
+ return {'avg_rps': 0, 'total_requests': 0}
267
+
268
+ avg_rps = total_requests / time_diff_seconds
269
+
270
+ # Get peak hourly throughput
271
+ hourly = qs.annotate(
272
+ hour=TruncHour('timestamp')
273
+ ).values('hour').annotate(
274
+ count=Count('id')
275
+ ).order_by('-count').first()
276
+
277
+ peak_rph = hourly['count'] if hourly else 0
278
+ peak_rps = peak_rph / 3600 # Convert to per second
279
+
280
+ return {
281
+ 'total_requests': total_requests,
282
+ 'avg_rps': round(avg_rps, 2),
283
+ 'avg_rpm': round(avg_rps * 60, 2),
284
+ 'peak_rps': round(peak_rps, 2),
285
+ 'peak_rph': peak_rph,
286
+ }