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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/ipc/RPC_LOGGING.md +321 -0
- django_cfg/apps/ipc/TESTING.md +539 -0
- django_cfg/apps/ipc/__init__.py +12 -3
- django_cfg/apps/ipc/admin.py +212 -0
- django_cfg/apps/ipc/migrations/0001_initial.py +137 -0
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +221 -0
- django_cfg/apps/ipc/serializers/__init__.py +10 -0
- django_cfg/apps/ipc/serializers/serializers.py +114 -0
- django_cfg/apps/ipc/services/client/client.py +83 -4
- django_cfg/apps/ipc/services/logging.py +239 -0
- django_cfg/apps/ipc/services/monitor.py +5 -3
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +269 -0
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +259 -0
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +375 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +22 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +9 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +9 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +23 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/stat_cards.html +50 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +47 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/tab_navigation.html +29 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +184 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +56 -0
- django_cfg/apps/ipc/urls.py +4 -2
- django_cfg/apps/ipc/views/__init__.py +7 -2
- django_cfg/apps/ipc/views/dashboard.py +1 -1
- django_cfg/apps/ipc/views/{viewsets.py → monitoring.py} +17 -11
- django_cfg/apps/ipc/views/testing.py +285 -0
- django_cfg/modules/django_client/system/generate_mjs_clients.py +1 -1
- django_cfg/modules/django_dashboard/sections/widgets.py +209 -0
- django_cfg/modules/django_unfold/callbacks/main.py +43 -18
- django_cfg/modules/django_unfold/dashboard.py +41 -4
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/js/api/index.mjs +8 -3
- django_cfg/static/js/api/ipc/client.mjs +40 -0
- django_cfg/static/js/api/knowbase/client.mjs +309 -0
- django_cfg/static/js/api/knowbase/index.mjs +13 -0
- django_cfg/static/js/api/payments/client.mjs +46 -1215
- django_cfg/static/js/api/types.mjs +164 -337
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +13 -1
- django_cfg/templates/admin/sections/widgets_section.html +129 -0
- django_cfg/templates/admin/snippets/tabs/widgets_tab.html +38 -0
- {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/METADATA +1 -1
- {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/RECORD +52 -28
- django_cfg/apps/ipc/templates/django_cfg_ipc/dashboard.html +0 -202
- /django_cfg/apps/ipc/static/django_cfg_ipc/js/{dashboard.mjs → dashboard.mjs.old} +0 -0
- /django_cfg/apps/ipc/templates/django_cfg_ipc/{base.html → layout/base.html} +0 -0
- {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/entry_points.txt +0 -0
- {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 %}
|
django_cfg/apps/ipc/urls.py
CHANGED
|
@@ -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.
|
|
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 .
|
|
6
|
+
from .monitoring import RPCMonitorViewSet
|
|
7
|
+
from .testing import RPCTestingViewSet
|
|
7
8
|
|
|
8
|
-
__all__ = [
|
|
9
|
+
__all__ = [
|
|
10
|
+
'RPCMonitorViewSet',
|
|
11
|
+
'RPCTestingViewSet',
|
|
12
|
+
'dashboard_view',
|
|
13
|
+
]
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
RPC Monitoring ViewSet.
|
|
3
3
|
|
|
4
|
-
Provides REST API endpoints
|
|
4
|
+
Provides REST API endpoints for monitoring RPC system health and statistics.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import
|
|
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 =
|
|
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 / "
|
|
57
|
+
example_django_dir = dev_dir.parent / "solution" / "projects" / "django"
|
|
58
58
|
|
|
59
59
|
# Output directory for MJS clients
|
|
60
60
|
if args.output:
|