django-cfg 1.4.59__py3-none-any.whl → 1.4.61__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.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (53) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/ipc/RPC_LOGGING.md +321 -0
  3. django_cfg/apps/ipc/TESTING.md +539 -0
  4. django_cfg/apps/ipc/__init__.py +12 -3
  5. django_cfg/apps/ipc/admin.py +212 -0
  6. django_cfg/apps/ipc/migrations/0001_initial.py +137 -0
  7. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  8. django_cfg/apps/ipc/models.py +221 -0
  9. django_cfg/apps/ipc/serializers/__init__.py +10 -0
  10. django_cfg/apps/ipc/serializers/serializers.py +114 -0
  11. django_cfg/apps/ipc/services/client/client.py +83 -4
  12. django_cfg/apps/ipc/services/logging.py +239 -0
  13. django_cfg/apps/ipc/services/monitor.py +5 -3
  14. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +269 -0
  15. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +259 -0
  16. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +375 -0
  17. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +22 -0
  18. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +9 -0
  19. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +9 -0
  20. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +23 -0
  21. django_cfg/apps/ipc/templates/django_cfg_ipc/components/stat_cards.html +50 -0
  22. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +47 -0
  23. django_cfg/apps/ipc/templates/django_cfg_ipc/components/tab_navigation.html +29 -0
  24. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +184 -0
  25. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +56 -0
  26. django_cfg/apps/ipc/urls.py +4 -2
  27. django_cfg/apps/ipc/views/__init__.py +7 -2
  28. django_cfg/apps/ipc/views/dashboard.py +1 -1
  29. django_cfg/apps/ipc/views/{viewsets.py → monitoring.py} +17 -11
  30. django_cfg/apps/ipc/views/testing.py +285 -0
  31. django_cfg/modules/django_client/system/generate_mjs_clients.py +1 -1
  32. django_cfg/modules/django_dashboard/sections/widgets.py +209 -0
  33. django_cfg/modules/django_unfold/callbacks/main.py +43 -18
  34. django_cfg/modules/django_unfold/dashboard.py +41 -4
  35. django_cfg/pyproject.toml +1 -1
  36. django_cfg/static/js/api/index.mjs +8 -3
  37. django_cfg/static/js/api/ipc/client.mjs +40 -0
  38. django_cfg/static/js/api/knowbase/client.mjs +309 -0
  39. django_cfg/static/js/api/knowbase/index.mjs +13 -0
  40. django_cfg/static/js/api/payments/client.mjs +46 -1215
  41. django_cfg/static/js/api/types.mjs +164 -337
  42. django_cfg/templates/admin/index.html +8 -0
  43. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +13 -1
  44. django_cfg/templates/admin/sections/widgets_section.html +129 -0
  45. django_cfg/templates/admin/snippets/tabs/widgets_tab.html +38 -0
  46. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/METADATA +1 -1
  47. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/RECORD +52 -28
  48. django_cfg/apps/ipc/templates/django_cfg_ipc/dashboard.html +0 -202
  49. /django_cfg/apps/ipc/static/django_cfg_ipc/js/{dashboard.mjs → dashboard.mjs.old} +0 -0
  50. /django_cfg/apps/ipc/templates/django_cfg_ipc/{base.html → layout/base.html} +0 -0
  51. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/WHEEL +0 -0
  52. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/entry_points.txt +0 -0
  53. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,184 @@
1
+ <!-- Testing Tools Tab -->
2
+ <div id="testing-tab" class="tab-panel hidden">
3
+ <!-- RPC Test Client -->
4
+ <div class="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700 mb-6">
5
+ <h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4 flex items-center">
6
+ <span class="material-icons mr-2 text-blue-500">send</span>
7
+ RPC Test Client
8
+ </h3>
9
+ <p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
10
+ Send individual RPC requests to test methods and inspect responses
11
+ </p>
12
+
13
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
14
+ <!-- Method Selection -->
15
+ <div>
16
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
17
+ RPC Method
18
+ </label>
19
+ <select id="test-rpc-method" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500">
20
+ <option value="">Select a method...</option>
21
+ <option value="notification.send">notification.send</option>
22
+ <option value="notification.broadcast">notification.broadcast</option>
23
+ <option value="workspace.file_changed">workspace.file_changed</option>
24
+ <option value="session.message">session.message</option>
25
+ </select>
26
+ </div>
27
+
28
+ <!-- Timeout -->
29
+ <div>
30
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
31
+ Timeout (seconds)
32
+ </label>
33
+ <input type="number" id="test-rpc-timeout" value="10" min="1" max="60" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500">
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Params JSON Editor -->
38
+ <div class="mb-4">
39
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
40
+ Parameters (JSON)
41
+ </label>
42
+ <textarea id="test-rpc-params" rows="7" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm focus:ring-blue-5 focus:border-blue-500">{
43
+ "user_id": "test-123",
44
+ "type": "info",
45
+ "title": "Test Notification",
46
+ "message": "This is a test message",
47
+ "timestamp": "2025-10-23T12:00:00.000Z"
48
+ }</textarea>
49
+ </div>
50
+
51
+ <!-- Action Buttons -->
52
+ <div class="flex gap-3">
53
+ <button id="send-test-rpc-btn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md font-medium flex items-center gap-2 transition-colors">
54
+ <span class="material-icons text-sm">send</span>
55
+ Send Request
56
+ </button>
57
+ <button id="clear-test-rpc-btn" class="px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md font-medium flex items-center gap-2 transition-colors">
58
+ <span class="material-icons text-sm">clear</span>
59
+ Clear
60
+ </button>
61
+ </div>
62
+
63
+ <!-- Response Display -->
64
+ <div id="test-rpc-result" class="mt-4 hidden">
65
+ </div>
66
+ </div>
67
+
68
+ <!-- Load Testing Tool -->
69
+ <div class="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700">
70
+ <h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4 flex items-center">
71
+ <span class="material-icons mr-2 text-orange-500">speed</span>
72
+ Load Testing Tool
73
+ </h3>
74
+ <p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
75
+ Emulate production load by sending multiple concurrent RPC requests
76
+ </p>
77
+
78
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
79
+ <!-- Total Requests -->
80
+ <div>
81
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
82
+ Total Requests
83
+ </label>
84
+ <input type="number" id="load-test-total" value="100" min="1" max="10000" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500">
85
+ </div>
86
+
87
+ <!-- Concurrency -->
88
+ <div>
89
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
90
+ Concurrent Requests
91
+ </label>
92
+ <input type="number" id="load-test-concurrency" value="10" min="1" max="100" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500">
93
+ </div>
94
+
95
+ <!-- Method -->
96
+ <div>
97
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
98
+ Test Method
99
+ </label>
100
+ <select id="load-test-method" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500">
101
+ <option value="">Select a method...</option>
102
+ <option value="notification.send">notification.send</option>
103
+ <option value="notification.broadcast">notification.broadcast</option>
104
+ <option value="workspace.file_changed">workspace.file_changed</option>
105
+ <option value="session.message">session.message</option>
106
+ </select>
107
+ </div>
108
+ </div>
109
+
110
+ <!-- Load Test Params JSON Editor -->
111
+ <div class="mb-4">
112
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
113
+ Request Parameters (JSON)
114
+ </label>
115
+ <textarea id="load-test-params" rows="5" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm focus:ring-blue-500 focus:border-blue-500">{
116
+ "user_id": "load-test-user",
117
+ "type": "info",
118
+ "title": "Load Test Notification",
119
+ "message": "This is a load test message",
120
+ "timestamp": "2025-10-23T12:00:00.000Z"
121
+ }</textarea>
122
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
123
+ Note: <code>_test_id</code> and <code>_index</code> will be automatically added to each request.
124
+ </p>
125
+ </div>
126
+
127
+ <!-- Action Buttons -->
128
+ <div class="flex gap-3 mb-4">
129
+ <button id="start-load-test-btn" class="px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white rounded-md font-medium flex items-center gap-2 transition-colors">
130
+ <span class="material-icons text-sm">play_arrow</span>
131
+ Start Load Test
132
+ </button>
133
+ <button id="stop-load-test-btn" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md font-medium flex items-center gap-2 transition-colors opacity-50 cursor-not-allowed" disabled>
134
+ <span class="material-icons text-sm">stop</span>
135
+ Stop Test
136
+ </button>
137
+ </div>
138
+
139
+ <!-- Progress -->
140
+ <div class="mb-4">
141
+ <div class="mb-2 flex justify-between text-sm">
142
+ <span class="text-gray-700 dark:text-gray-300">Progress: <span id="load-test-progress-text">0/0</span></span>
143
+ </div>
144
+ <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5 mb-4">
145
+ <div id="load-test-progress" class="bg-orange-600 h-2.5 rounded-full transition-all" style="width: 0%"></div>
146
+ </div>
147
+ </div>
148
+
149
+ <!-- Statistics Cards -->
150
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
151
+ <div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 border border-blue-200 dark:border-blue-800">
152
+ <div class="flex items-center justify-between mb-2">
153
+ <span class="material-icons text-xl text-blue-600 dark:text-blue-400">check_circle</span>
154
+ </div>
155
+ <div class="text-2xl font-bold text-gray-900 dark:text-white" id="load-test-success">0</div>
156
+ <div class="text-xs text-gray-600 dark:text-gray-400">Success</div>
157
+ </div>
158
+
159
+ <div class="bg-red-50 dark:bg-red-900/20 rounded-lg p-4 border border-red-200 dark:border-red-800">
160
+ <div class="flex items-center justify-between mb-2">
161
+ <span class="material-icons text-xl text-red-600 dark:text-red-400">error</span>
162
+ </div>
163
+ <div class="text-2xl font-bold text-gray-900 dark:text-white" id="load-test-failed">0</div>
164
+ <div class="text-xs text-gray-600 dark:text-gray-400">Failed</div>
165
+ </div>
166
+
167
+ <div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-4 border border-green-200 dark:border-green-800">
168
+ <div class="flex items-center justify-between mb-2">
169
+ <span class="material-icons text-xl text-green-600 dark:text-green-400">timer</span>
170
+ </div>
171
+ <div class="text-2xl font-bold text-gray-900 dark:text-white"><span id="load-test-avg-time">0</span>ms</div>
172
+ <div class="text-xs text-gray-600 dark:text-gray-400">Avg Time</div>
173
+ </div>
174
+
175
+ <div class="bg-purple-50 dark:bg-purple-900/20 rounded-lg p-4 border border-purple-200 dark:border-purple-800">
176
+ <div class="flex items-center justify-between mb-2">
177
+ <span class="material-icons text-xl text-purple-600 dark:text-purple-400">trending_up</span>
178
+ </div>
179
+ <div class="text-2xl font-bold text-gray-900 dark:text-white" id="load-test-rps">0</div>
180
+ <div class="text-xs text-gray-600 dark:text-gray-400">RPS</div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </div>
@@ -0,0 +1,56 @@
1
+ {% extends 'django_cfg_ipc/layout/base.html' %}
2
+ {% load static %}
3
+
4
+ {% block title %}RPC Monitor Dashboard{% endblock %}
5
+
6
+ {% block content %}
7
+ <!-- Statistics Cards -->
8
+ {% include 'django_cfg_ipc/components/stat_cards.html' %}
9
+
10
+ <!-- System Status -->
11
+ {% include 'django_cfg_ipc/components/system_status.html' %}
12
+
13
+ <!-- Tab Navigation -->
14
+ {% include 'django_cfg_ipc/components/tab_navigation.html' %}
15
+
16
+ <!-- Tab Content -->
17
+ <div class="tab-content">
18
+ {% include 'django_cfg_ipc/components/overview_content.html' %}
19
+ {% include 'django_cfg_ipc/components/requests_content.html' %}
20
+ {% include 'django_cfg_ipc/components/notifications_content.html' %}
21
+ {% include 'django_cfg_ipc/components/methods_content.html' %}
22
+ {% include 'django_cfg_ipc/components/testing_tools.html' %}
23
+ </div>
24
+ {% endblock %}
25
+
26
+ {% block extra_js %}
27
+ {% load i18n %}
28
+ {% now "U" as current_timestamp %}
29
+
30
+ <!-- Load and initialize IPC API -->
31
+ <script type="module">
32
+ try {
33
+ console.log('🔄 Starting IpcAPI module import...');
34
+ const { loadAPI, showNotification } = await import('/static/js/api-loader.mjs');
35
+
36
+ // Load ipc API
37
+ const ipcAPI = await loadAPI('ipc');
38
+ console.log('✅ IpcAPI module imported successfully:', ipcAPI);
39
+
40
+ // Make it available globally as ipcAPI
41
+ window.ipcAPI = ipcAPI;
42
+ window.showNotification = showNotification;
43
+
44
+ console.log('✅ IpcAPI loaded from generated MJS client');
45
+ } catch (error) {
46
+ console.error('❌ Failed to load IpcAPI module:', error);
47
+ console.error('Error stack:', error.stack);
48
+ window.ipcAPI = null;
49
+ }
50
+ </script>
51
+
52
+ <!-- Dashboard Modules -->
53
+ <script type="module" src="{% static 'django_cfg_ipc/js/dashboard/overview.mjs' %}?v={{ current_timestamp }}"></script>
54
+ <script type="module" src="{% static 'django_cfg_ipc/js/dashboard/testing.mjs' %}?v={{ current_timestamp }}"></script>
55
+ <script type="module" src="{% static 'django_cfg_ipc/js/dashboard/main.mjs' %}?v={{ current_timestamp }}"></script>
56
+ {% endblock %}
@@ -1,19 +1,21 @@
1
1
  """
2
2
  URL patterns for IPC/RPC module.
3
3
 
4
- Public API endpoints for RPC monitoring using DRF router.
4
+ Public API endpoints for RPC monitoring and testing using DRF router.
5
5
  """
6
6
 
7
7
  from django.urls import include, path
8
8
  from rest_framework import routers
9
9
 
10
- from .views.viewsets import RPCMonitorViewSet
10
+ from .views.monitoring import RPCMonitorViewSet
11
+ from .views.testing import RPCTestingViewSet
11
12
 
12
13
  app_name = 'django_cfg_ipc'
13
14
 
14
15
  # Create router
15
16
  router = routers.DefaultRouter()
16
17
  router.register(r'monitor', RPCMonitorViewSet, basename='monitor')
18
+ router.register(r'test', RPCTestingViewSet, basename='test')
17
19
 
18
20
  urlpatterns = [
19
21
  # Include router URLs
@@ -3,6 +3,11 @@ Views for IPC/RPC module.
3
3
  """
4
4
 
5
5
  from .dashboard import dashboard_view
6
- from .viewsets import RPCMonitorViewSet
6
+ from .monitoring import RPCMonitorViewSet
7
+ from .testing import RPCTestingViewSet
7
8
 
8
- __all__ = ['RPCMonitorViewSet', 'dashboard_view']
9
+ __all__ = [
10
+ 'RPCMonitorViewSet',
11
+ 'RPCTestingViewSet',
12
+ 'dashboard_view',
13
+ ]
@@ -12,4 +12,4 @@ def dashboard_view(request):
12
12
  context = {
13
13
  'page_title': 'IPC/RPC Monitor Dashboard',
14
14
  }
15
- return render(request, 'django_cfg_ipc/dashboard.html', context)
15
+ return render(request, 'django_cfg_ipc/pages/dashboard.html', context)
@@ -1,14 +1,14 @@
1
1
  """
2
- DRF ViewSets for IPC/RPC module.
2
+ RPC Monitoring ViewSet.
3
3
 
4
- Provides REST API endpoints with OpenAPI documentation using ViewSets and Routers.
4
+ Provides REST API endpoints for monitoring RPC system health and statistics.
5
5
  """
6
6
 
7
- import logging
8
-
7
+ from django_cfg.modules.django_logging import get_logger
9
8
  from drf_spectacular.types import OpenApiTypes
10
9
  from drf_spectacular.utils import OpenApiParameter, extend_schema
11
10
  from rest_framework import status, viewsets
11
+ from rest_framework.authentication import SessionAuthentication
12
12
  from rest_framework.decorators import action
13
13
  from rest_framework.permissions import IsAdminUser
14
14
  from rest_framework.response import Response
@@ -22,20 +22,26 @@ from ..serializers import (
22
22
  )
23
23
  from ..services import RPCMonitor
24
24
 
25
- logger = logging.getLogger(__name__)
25
+ logger = get_logger("ipc.monitoring")
26
26
 
27
27
 
28
28
  class RPCMonitorViewSet(viewsets.ViewSet):
29
29
  """
30
30
  ViewSet for RPC monitoring and statistics.
31
31
 
32
- Provides comprehensive monitoring data for the RPC system.
32
+ Provides comprehensive monitoring data for the RPC system including:
33
+ - Health checks
34
+ - Overview statistics
35
+ - Recent requests
36
+ - Notification stats
37
+ - Method-level statistics
33
38
  """
34
39
 
40
+ authentication_classes = [SessionAuthentication]
35
41
  permission_classes = [IsAdminUser]
36
42
 
37
43
  @extend_schema(
38
- tags=['IPC/RPC'],
44
+ tags=['IPC/RPC Monitoring'],
39
45
  summary="Get RPC health status",
40
46
  description="Returns the current health status of the RPC monitoring system.",
41
47
  responses={
@@ -69,7 +75,7 @@ class RPCMonitorViewSet(viewsets.ViewSet):
69
75
  )
70
76
 
71
77
  @extend_schema(
72
- tags=['IPC/RPC'],
78
+ tags=['IPC/RPC Monitoring'],
73
79
  summary="Get overview statistics",
74
80
  description="Returns overview statistics for RPC monitoring.",
75
81
  responses={
@@ -109,7 +115,7 @@ class RPCMonitorViewSet(viewsets.ViewSet):
109
115
  )
110
116
 
111
117
  @extend_schema(
112
- tags=['IPC/RPC'],
118
+ tags=['IPC/RPC Monitoring'],
113
119
  summary="Get recent RPC requests",
114
120
  description="Returns a list of recent RPC requests with their details.",
115
121
  parameters=[
@@ -168,7 +174,7 @@ class RPCMonitorViewSet(viewsets.ViewSet):
168
174
  )
169
175
 
170
176
  @extend_schema(
171
- tags=['IPC/RPC'],
177
+ tags=['IPC/RPC Monitoring'],
172
178
  summary="Get notification statistics",
173
179
  description="Returns statistics about RPC notifications.",
174
180
  responses={
@@ -202,7 +208,7 @@ class RPCMonitorViewSet(viewsets.ViewSet):
202
208
  )
203
209
 
204
210
  @extend_schema(
205
- tags=['IPC/RPC'],
211
+ tags=['IPC/RPC Monitoring'],
206
212
  summary="Get method statistics",
207
213
  description="Returns statistics grouped by RPC method.",
208
214
  responses={
@@ -0,0 +1,285 @@
1
+ """
2
+ RPC Testing ViewSet.
3
+
4
+ Provides REST API endpoints for testing RPC system with load testing capabilities.
5
+ """
6
+
7
+ import time
8
+ import uuid
9
+ from threading import Thread
10
+
11
+ from django_cfg.modules.django_logging import get_logger
12
+ from drf_spectacular.utils import extend_schema
13
+ from rest_framework import status, viewsets
14
+ from rest_framework.authentication import SessionAuthentication
15
+ from rest_framework.decorators import action
16
+ from rest_framework.permissions import IsAdminUser
17
+ from rest_framework.response import Response
18
+
19
+ from ..serializers import (
20
+ LoadTestRequestSerializer,
21
+ LoadTestResponseSerializer,
22
+ LoadTestStatusSerializer,
23
+ TestRPCRequestSerializer,
24
+ TestRPCResponseSerializer,
25
+ )
26
+
27
+ logger = get_logger("ipc.testing")
28
+
29
+ # Global load test state
30
+ _load_test_state = {
31
+ 'test_id': None,
32
+ 'running': False,
33
+ 'progress': 0,
34
+ 'total': 0,
35
+ 'success_count': 0,
36
+ 'failed_count': 0,
37
+ 'durations': [],
38
+ 'start_time': None,
39
+ }
40
+
41
+
42
+ class RPCTestingViewSet(viewsets.ViewSet):
43
+ """
44
+ ViewSet for RPC testing tools.
45
+
46
+ Provides endpoints for:
47
+ - Sending test RPC requests
48
+ - Running load tests with concurrent requests
49
+ - Monitoring load test progress
50
+ """
51
+
52
+ authentication_classes = [SessionAuthentication]
53
+ permission_classes = [IsAdminUser]
54
+ serializer_class = TestRPCRequestSerializer # Default serializer for schema generation
55
+
56
+ @extend_schema(
57
+ tags=['IPC/RPC Testing'],
58
+ summary="Send test RPC request",
59
+ description="Send a single RPC request for testing purposes and measure response time.",
60
+ request=TestRPCRequestSerializer,
61
+ responses={
62
+ 200: TestRPCResponseSerializer,
63
+ 400: {"description": "Invalid parameters"},
64
+ 500: {"description": "RPC call failed"},
65
+ },
66
+ )
67
+ @action(detail=False, methods=['post'], url_path='send')
68
+ def test_send(self, request):
69
+ """Send a test RPC request and return response with timing."""
70
+ serializer = TestRPCRequestSerializer(data=request.data)
71
+
72
+ if not serializer.is_valid():
73
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
74
+
75
+ method = serializer.validated_data['method']
76
+ params = serializer.validated_data['params']
77
+ timeout = serializer.validated_data.get('timeout', 10)
78
+
79
+ correlation_id = str(uuid.uuid4())
80
+ start_time = time.time()
81
+
82
+ try:
83
+ from django_cfg.apps.ipc import get_rpc_client
84
+
85
+ rpc_client = get_rpc_client()
86
+ result = rpc_client.call(
87
+ method=method,
88
+ params=params,
89
+ timeout=timeout,
90
+ user=request.user
91
+ )
92
+
93
+ duration_ms = (time.time() - start_time) * 1000
94
+
95
+ response_data = {
96
+ 'success': True,
97
+ 'duration_ms': round(duration_ms, 2),
98
+ 'response': result,
99
+ 'error': None,
100
+ 'correlation_id': correlation_id,
101
+ }
102
+
103
+ return Response(response_data)
104
+
105
+ except Exception as e:
106
+ duration_ms = (time.time() - start_time) * 1000
107
+ logger.error(f"Test RPC call failed: {e}", exc_info=True)
108
+
109
+ response_data = {
110
+ 'success': False,
111
+ 'duration_ms': round(duration_ms, 2),
112
+ 'response': None,
113
+ 'error': str(e),
114
+ 'correlation_id': correlation_id,
115
+ }
116
+
117
+ return Response(response_data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
118
+
119
+ @extend_schema(
120
+ tags=['IPC/RPC Testing'],
121
+ summary="Start load test",
122
+ description="Start a load test by sending multiple concurrent RPC requests.",
123
+ request=LoadTestRequestSerializer,
124
+ responses={
125
+ 200: LoadTestResponseSerializer,
126
+ 400: {"description": "Invalid parameters"},
127
+ 409: {"description": "Load test already running"},
128
+ },
129
+ )
130
+ @action(detail=False, methods=['post'], url_path='load/start')
131
+ def load_test_start(self, request):
132
+ """Start a load test."""
133
+ global _load_test_state
134
+
135
+ if _load_test_state['running']:
136
+ return Response(
137
+ {'error': 'Load test already running'},
138
+ status=status.HTTP_409_CONFLICT
139
+ )
140
+
141
+ serializer = LoadTestRequestSerializer(data=request.data)
142
+ if not serializer.is_valid():
143
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
144
+
145
+ test_id = str(uuid.uuid4())[:8]
146
+ method = serializer.validated_data['method']
147
+ total_requests = serializer.validated_data['total_requests']
148
+ concurrency = serializer.validated_data['concurrency']
149
+ params = serializer.validated_data.get('params', {})
150
+
151
+ # Reset state
152
+ _load_test_state.update({
153
+ 'test_id': test_id,
154
+ 'running': True,
155
+ 'progress': 0,
156
+ 'total': total_requests,
157
+ 'success_count': 0,
158
+ 'failed_count': 0,
159
+ 'durations': [],
160
+ 'start_time': time.time(),
161
+ })
162
+
163
+ # Start load test in background
164
+ def run_load_test():
165
+ from django_cfg.apps.ipc import get_rpc_client
166
+
167
+ try:
168
+ rpc_client = get_rpc_client()
169
+
170
+ def send_request(index):
171
+ try:
172
+ start = time.time()
173
+ rpc_client.call(
174
+ method=method,
175
+ params={**params, '_test_id': test_id, '_index': index},
176
+ timeout=10,
177
+ user=request.user
178
+ )
179
+ duration = (time.time() - start) * 1000
180
+
181
+ _load_test_state['success_count'] += 1
182
+ _load_test_state['durations'].append(duration)
183
+ except Exception as e:
184
+ logger.warning(f"Load test request {index} failed: {e}")
185
+ _load_test_state['failed_count'] += 1
186
+ finally:
187
+ _load_test_state['progress'] += 1
188
+
189
+ # Send requests in batches based on concurrency
190
+ for i in range(0, total_requests, concurrency):
191
+ if not _load_test_state['running']:
192
+ break
193
+
194
+ batch_size = min(concurrency, total_requests - i)
195
+ threads = []
196
+
197
+ for j in range(batch_size):
198
+ thread = Thread(target=send_request, args=(i + j,))
199
+ thread.start()
200
+ threads.append(thread)
201
+
202
+ for thread in threads:
203
+ thread.join()
204
+
205
+ except Exception as e:
206
+ logger.error(f"Load test failed: {e}", exc_info=True)
207
+ finally:
208
+ _load_test_state['running'] = False
209
+
210
+ thread = Thread(target=run_load_test)
211
+ thread.daemon = True
212
+ thread.start()
213
+
214
+ return Response({
215
+ 'test_id': test_id,
216
+ 'started': True,
217
+ 'message': f'Load test started with {total_requests} requests at {concurrency} concurrency'
218
+ })
219
+
220
+ @extend_schema(
221
+ tags=['IPC/RPC Testing'],
222
+ summary="Get load test status",
223
+ description="Get current status of running or completed load test.",
224
+ responses={
225
+ 200: LoadTestStatusSerializer,
226
+ },
227
+ )
228
+ @action(detail=False, methods=['get'], url_path='load/status')
229
+ def load_test_status(self, request):
230
+ """Get status of current load test."""
231
+ global _load_test_state
232
+
233
+ elapsed_time = 0
234
+ if _load_test_state['start_time']:
235
+ elapsed_time = time.time() - _load_test_state['start_time']
236
+
237
+ avg_duration = 0
238
+ if _load_test_state['durations']:
239
+ avg_duration = sum(_load_test_state['durations']) / len(_load_test_state['durations'])
240
+
241
+ rps = 0
242
+ if elapsed_time > 0:
243
+ rps = _load_test_state['progress'] / elapsed_time
244
+
245
+ response_data = {
246
+ 'test_id': _load_test_state['test_id'],
247
+ 'running': _load_test_state['running'],
248
+ 'progress': _load_test_state['progress'],
249
+ 'total': _load_test_state['total'],
250
+ 'success_count': _load_test_state['success_count'],
251
+ 'failed_count': _load_test_state['failed_count'],
252
+ 'avg_duration_ms': round(avg_duration, 2),
253
+ 'elapsed_time': round(elapsed_time, 2),
254
+ 'rps': round(rps, 2),
255
+ }
256
+
257
+ return Response(response_data)
258
+
259
+ @extend_schema(
260
+ tags=['IPC/RPC Testing'],
261
+ summary="Stop load test",
262
+ description="Stop currently running load test.",
263
+ responses={
264
+ 200: {"description": "Load test stopped"},
265
+ 400: {"description": "No load test running"},
266
+ },
267
+ )
268
+ @action(detail=False, methods=['post'], url_path='load/stop')
269
+ def load_test_stop(self, request):
270
+ """Stop current load test."""
271
+ global _load_test_state
272
+
273
+ if not _load_test_state['running']:
274
+ return Response(
275
+ {'message': 'No load test currently running'},
276
+ status=status.HTTP_400_BAD_REQUEST
277
+ )
278
+
279
+ _load_test_state['running'] = False
280
+
281
+ return Response({
282
+ 'message': 'Load test stopped',
283
+ 'progress': _load_test_state['progress'],
284
+ 'total': _load_test_state['total']
285
+ })
@@ -54,7 +54,7 @@ def main():
54
54
  django_cfg_dir = modules_dir.parent # django_cfg
55
55
  src_dir = django_cfg_dir.parent # src
56
56
  dev_dir = src_dir.parent # django-cfg-dev
57
- example_django_dir = dev_dir.parent / "django-cfg-example" / "django"
57
+ example_django_dir = dev_dir.parent / "solution" / "projects" / "django"
58
58
 
59
59
  # Output directory for MJS clients
60
60
  if args.output: