jettask 0.2.5__py3-none-any.whl → 0.2.7__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 (93) hide show
  1. jettask/monitor/run_backlog_collector.py +96 -0
  2. jettask/monitor/stream_backlog_monitor.py +362 -0
  3. jettask/pg_consumer/pg_consumer_v2.py +403 -0
  4. jettask/pg_consumer/sql_utils.py +182 -0
  5. jettask/scheduler/__init__.py +17 -0
  6. jettask/scheduler/add_execution_count.sql +11 -0
  7. jettask/scheduler/add_priority_field.sql +26 -0
  8. jettask/scheduler/add_scheduler_id.sql +25 -0
  9. jettask/scheduler/add_scheduler_id_index.sql +10 -0
  10. jettask/scheduler/loader.py +249 -0
  11. jettask/scheduler/make_scheduler_id_required.sql +28 -0
  12. jettask/scheduler/manager.py +696 -0
  13. jettask/scheduler/migrate_interval_seconds.sql +9 -0
  14. jettask/scheduler/models.py +200 -0
  15. jettask/scheduler/multi_namespace_scheduler.py +294 -0
  16. jettask/scheduler/performance_optimization.sql +45 -0
  17. jettask/scheduler/run_scheduler.py +186 -0
  18. jettask/scheduler/scheduler.py +715 -0
  19. jettask/scheduler/schema.sql +84 -0
  20. jettask/scheduler/unified_manager.py +450 -0
  21. jettask/scheduler/unified_scheduler_manager.py +280 -0
  22. jettask/webui/backend/api/__init__.py +3 -0
  23. jettask/webui/backend/api/v1/__init__.py +17 -0
  24. jettask/webui/backend/api/v1/monitoring.py +431 -0
  25. jettask/webui/backend/api/v1/namespaces.py +504 -0
  26. jettask/webui/backend/api/v1/queues.py +342 -0
  27. jettask/webui/backend/api/v1/tasks.py +367 -0
  28. jettask/webui/backend/core/__init__.py +3 -0
  29. jettask/webui/backend/core/cache.py +221 -0
  30. jettask/webui/backend/core/database.py +200 -0
  31. jettask/webui/backend/core/exceptions.py +102 -0
  32. jettask/webui/backend/models/__init__.py +3 -0
  33. jettask/webui/backend/models/requests.py +236 -0
  34. jettask/webui/backend/models/responses.py +230 -0
  35. jettask/webui/backend/services/__init__.py +3 -0
  36. jettask/webui/frontend/index.html +13 -0
  37. jettask/webui/models/__init__.py +3 -0
  38. jettask/webui/models/namespace.py +63 -0
  39. jettask/webui/sql/batch_upsert_functions.sql +178 -0
  40. jettask/webui/sql/init_database.sql +640 -0
  41. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/METADATA +80 -10
  42. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/RECORD +46 -53
  43. jettask/webui/frontend/package-lock.json +0 -4833
  44. jettask/webui/frontend/package.json +0 -30
  45. jettask/webui/frontend/src/App.css +0 -109
  46. jettask/webui/frontend/src/App.jsx +0 -66
  47. jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -166
  48. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -298
  49. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -638
  50. jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -65
  51. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -487
  52. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -465
  53. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -423
  54. jettask/webui/frontend/src/components/TaskFilter.jsx +0 -425
  55. jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -21
  56. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -160
  57. jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -111
  58. jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -115
  59. jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -40
  60. jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -18
  61. jettask/webui/frontend/src/components/layout/AppLayout.css +0 -95
  62. jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -49
  63. jettask/webui/frontend/src/components/layout/Header.css +0 -106
  64. jettask/webui/frontend/src/components/layout/Header.jsx +0 -106
  65. jettask/webui/frontend/src/components/layout/SideMenu.css +0 -137
  66. jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -209
  67. jettask/webui/frontend/src/components/layout/TabsNav.css +0 -244
  68. jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -206
  69. jettask/webui/frontend/src/components/layout/UserInfo.css +0 -197
  70. jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -197
  71. jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -27
  72. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -72
  73. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -245
  74. jettask/webui/frontend/src/index.css +0 -114
  75. jettask/webui/frontend/src/main.jsx +0 -20
  76. jettask/webui/frontend/src/pages/Alerts.jsx +0 -684
  77. jettask/webui/frontend/src/pages/Dashboard/index.css +0 -35
  78. jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -281
  79. jettask/webui/frontend/src/pages/Dashboard.jsx +0 -1330
  80. jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -1117
  81. jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -527
  82. jettask/webui/frontend/src/pages/Queues.jsx +0 -12
  83. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +0 -809
  84. jettask/webui/frontend/src/pages/Settings.jsx +0 -800
  85. jettask/webui/frontend/src/pages/Workers.jsx +0 -12
  86. jettask/webui/frontend/src/services/api.js +0 -114
  87. jettask/webui/frontend/src/services/queueTrend.js +0 -152
  88. jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
  89. jettask/webui/frontend/src/utils/userPreferences.js +0 -154
  90. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/WHEEL +0 -0
  91. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/entry_points.txt +0 -0
  92. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/licenses/LICENSE +0 -0
  93. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,280 @@
1
+ """
2
+ 统一的定时任务调度管理器
3
+ 自动识别单命名空间和多命名空间模式
4
+ """
5
+ import asyncio
6
+ import logging
7
+ import multiprocessing
8
+ from typing import Dict, Optional, Set
9
+ from jettask.core.unified_manager_base import UnifiedManagerBase
10
+ from jettask import Jettask
11
+ from jettask.webui.task_center import TaskCenter
12
+ from .scheduler import TaskScheduler
13
+ from .manager import ScheduledTaskManager
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class UnifiedSchedulerManager(UnifiedManagerBase):
19
+ """
20
+ 统一的调度器管理器
21
+ 继承自 UnifiedManagerBase,实现调度器特定的逻辑
22
+ """
23
+
24
+ def __init__(self,
25
+ task_center_url: str,
26
+ scan_interval: float = 0.1,
27
+ batch_size: int = 100,
28
+ check_interval: int = 30,
29
+ debug: bool = False):
30
+ """
31
+ 初始化调度器管理器
32
+
33
+ Args:
34
+ task_center_url: 任务中心URL
35
+ scan_interval: 调度器扫描间隔(秒)
36
+ batch_size: 每批处理的最大任务数
37
+ check_interval: 命名空间检测间隔(秒)
38
+ debug: 是否启用调试模式
39
+ """
40
+ super().__init__(task_center_url, check_interval, debug)
41
+
42
+ self.scan_interval = scan_interval
43
+ self.batch_size = batch_size
44
+
45
+ # 调度器管理
46
+ self.scheduler_instance: Optional[TaskScheduler] = None # 单命名空间模式
47
+ self.scheduler_processes: Dict[str, multiprocessing.Process] = {} # 多命名空间模式
48
+ self.known_namespaces: Set[str] = set()
49
+
50
+ async def run_single_namespace(self, namespace_name: str):
51
+ """
52
+ 运行单命名空间模式
53
+
54
+ Args:
55
+ namespace_name: 命名空间名称
56
+ """
57
+ logger.info(f"启动单命名空间调度器: {namespace_name}")
58
+ logger.info(f"扫描间隔: {self.scan_interval}秒")
59
+ logger.info(f"批处理大小: {self.batch_size}")
60
+
61
+ try:
62
+ # 创建任务中心连接
63
+ tc = TaskCenter(self.task_center_url)
64
+ if not tc._connect_sync():
65
+ raise Exception(f"无法连接到任务中心: {self.task_center_url}")
66
+
67
+ # 创建 Jettask 应用
68
+ app = Jettask(task_center=tc)
69
+
70
+ # 创建调度器管理器
71
+ manager = ScheduledTaskManager(app, namespace=namespace_name)
72
+
73
+ # 创建并启动调度器
74
+ self.scheduler_instance = TaskScheduler(
75
+ manager=manager,
76
+ scan_interval=self.scan_interval,
77
+ batch_size=self.batch_size
78
+ )
79
+
80
+ # 运行调度器
81
+ await self.scheduler_instance.start()
82
+
83
+ except Exception as e:
84
+ logger.error(f"单命名空间调度器运行失败: {e}", exc_info=self.debug)
85
+ raise
86
+
87
+ async def run_multi_namespace(self, namespace_names: Optional[Set[str]]):
88
+ """
89
+ 运行多命名空间模式
90
+
91
+ Args:
92
+ namespace_names: 目标命名空间集合,None表示所有命名空间
93
+ """
94
+ logger.info("启动多命名空间调度器管理")
95
+ logger.info(f"扫描间隔: {self.scan_interval}秒")
96
+ logger.info(f"批处理大小: {self.batch_size}")
97
+
98
+ # 获取初始命名空间
99
+ namespaces = await self.fetch_namespaces_info(namespace_names)
100
+
101
+ # 启动每个命名空间的调度器进程
102
+ for ns_info in namespaces:
103
+ self._start_scheduler_process(ns_info['name'])
104
+ self.known_namespaces.add(ns_info['name'])
105
+
106
+ # 创建并发任务
107
+ try:
108
+ health_check_task = asyncio.create_task(self._health_check_loop())
109
+ namespace_check_task = asyncio.create_task(self._namespace_check_loop())
110
+
111
+ # 等待任一任务完成或出错
112
+ done, pending = await asyncio.wait(
113
+ [health_check_task, namespace_check_task],
114
+ return_when=asyncio.FIRST_EXCEPTION
115
+ )
116
+
117
+ # 取消所有未完成的任务
118
+ for task in pending:
119
+ task.cancel()
120
+
121
+ except asyncio.CancelledError:
122
+ logger.info("收到取消信号")
123
+
124
+ def _start_scheduler_process(self, namespace_name: str):
125
+ """启动单个命名空间的调度器进程"""
126
+
127
+ # 如果进程已存在且存活,跳过
128
+ if namespace_name in self.scheduler_processes:
129
+ process = self.scheduler_processes[namespace_name]
130
+ if process.is_alive():
131
+ logger.debug(f"命名空间 {namespace_name} 的调度器进程已在运行")
132
+ return
133
+ else:
134
+ # 清理已停止的进程
135
+ process.terminate()
136
+ process.join(timeout=5)
137
+
138
+ # 创建新进程
139
+ process = multiprocessing.Process(
140
+ target=self._run_scheduler_for_namespace,
141
+ args=(namespace_name, self.task_center_url, self.scan_interval, self.batch_size),
142
+ name=f"scheduler_{namespace_name}"
143
+ )
144
+ process.daemon = False
145
+ process.start()
146
+
147
+ self.scheduler_processes[namespace_name] = process
148
+ logger.info(f"启动命名空间 {namespace_name} 的调度器进程, PID: {process.pid}")
149
+
150
+ @staticmethod
151
+ def _run_scheduler_for_namespace(namespace_name: str, task_center_url: str,
152
+ scan_interval: float, batch_size: int):
153
+ """在独立进程中运行单个命名空间的调度器"""
154
+ import asyncio
155
+ import signal
156
+ import sys
157
+
158
+ # 设置信号处理
159
+ def signal_handler(signum, frame):
160
+ logger.info(f"命名空间 {namespace_name} 的调度器进程收到信号 {signum}")
161
+ sys.exit(0)
162
+
163
+ signal.signal(signal.SIGTERM, signal_handler)
164
+ signal.signal(signal.SIGINT, signal_handler)
165
+
166
+ # 创建新的事件循环
167
+ loop = asyncio.new_event_loop()
168
+ asyncio.set_event_loop(loop)
169
+
170
+ async def run_scheduler():
171
+ try:
172
+ # 构建命名空间特定的URL
173
+ if '/api/namespaces/' not in task_center_url:
174
+ # 如果是基础URL,添加命名空间路径
175
+ url = f"{task_center_url}/api/namespaces/{namespace_name}"
176
+ else:
177
+ # 替换现有的命名空间
178
+ base_url = task_center_url.split('/api/namespaces/')[0]
179
+ url = f"{base_url}/api/namespaces/{namespace_name}"
180
+
181
+ # 创建任务中心连接
182
+ tc = TaskCenter(url)
183
+ if not tc._connect_sync():
184
+ raise Exception(f"无法连接到任务中心: {url}")
185
+
186
+ # 创建 Jettask 应用
187
+ app = Jettask(task_center=tc)
188
+
189
+ # 创建调度器管理器 - ScheduledTaskManager 只接受一个参数
190
+ manager = ScheduledTaskManager(app)
191
+
192
+ # 创建并启动调度器 - 使用正确的参数名
193
+ scheduler = TaskScheduler(
194
+ app=app,
195
+ db_manager=manager,
196
+ scan_interval=scan_interval,
197
+ batch_size=batch_size
198
+ )
199
+
200
+ logger.info(f"命名空间 {namespace_name} 的调度器已启动")
201
+ await scheduler.run()
202
+
203
+ except Exception as e:
204
+ logger.error(f"命名空间 {namespace_name} 的调度器启动失败: {e}")
205
+ raise
206
+
207
+ try:
208
+ loop.run_until_complete(run_scheduler())
209
+ except KeyboardInterrupt:
210
+ logger.info(f"命名空间 {namespace_name} 的调度器进程收到中断信号")
211
+ finally:
212
+ loop.close()
213
+
214
+ async def _health_check_loop(self):
215
+ """健康检查循环"""
216
+ while self.running:
217
+ try:
218
+ await asyncio.sleep(30) # 每30秒检查一次
219
+
220
+ # 检查所有调度器进程的健康状态
221
+ for namespace_name, process in list(self.scheduler_processes.items()):
222
+ if not process.is_alive():
223
+ logger.warning(f"命名空间 {namespace_name} 的调度器进程已停止,尝试重启")
224
+ self._start_scheduler_process(namespace_name)
225
+
226
+ except Exception as e:
227
+ logger.error(f"健康检查错误: {e}")
228
+
229
+ async def _namespace_check_loop(self):
230
+ """命名空间检测循环(动态添加/移除)"""
231
+ while self.running:
232
+ try:
233
+ await asyncio.sleep(self.check_interval)
234
+
235
+ # 获取当前所有命名空间
236
+ current_namespaces = await self.fetch_namespaces_info()
237
+ current_names = {ns['name'] for ns in current_namespaces}
238
+
239
+ # 检测新增的命名空间
240
+ new_names = current_names - self.known_namespaces
241
+ for name in new_names:
242
+ logger.info(f"检测到新命名空间: {name}")
243
+ self._start_scheduler_process(name)
244
+ self.known_namespaces.add(name)
245
+
246
+ # 检测删除的命名空间
247
+ removed_names = self.known_namespaces - current_names
248
+ for name in removed_names:
249
+ logger.info(f"检测到命名空间已删除: {name}")
250
+ if name in self.scheduler_processes:
251
+ process = self.scheduler_processes[name]
252
+ process.terminate()
253
+ process.join(timeout=5)
254
+ del self.scheduler_processes[name]
255
+ self.known_namespaces.discard(name)
256
+
257
+ except Exception as e:
258
+ logger.error(f"命名空间检测错误: {e}")
259
+
260
+ async def cleanup(self):
261
+ """清理资源"""
262
+ if self.scheduler_instance:
263
+ # 单命名空间模式
264
+ logger.info("停止调度器")
265
+ await self.scheduler_instance.stop()
266
+
267
+ # 多命名空间模式
268
+ logger.info("停止所有调度器进程")
269
+ for namespace_name, process in self.scheduler_processes.items():
270
+ try:
271
+ process.terminate()
272
+ process.join(timeout=5)
273
+ if process.is_alive():
274
+ process.kill()
275
+ process.join()
276
+ logger.info(f"停止命名空间 {namespace_name} 的调度器进程")
277
+ except Exception as e:
278
+ logger.error(f"停止命名空间 {namespace_name} 的调度器进程失败: {e}")
279
+
280
+ self.scheduler_processes.clear()
@@ -0,0 +1,3 @@
1
+ """
2
+ API routes for JetTask WebUI Backend
3
+ """
@@ -0,0 +1,17 @@
1
+ """
2
+ API v1 routes
3
+ """
4
+ from fastapi import APIRouter
5
+ from .queues import router as queues_router
6
+ from .tasks import router as tasks_router
7
+ from .monitoring import router as monitoring_router
8
+ from .namespaces import router as namespaces_router
9
+
10
+ # 创建v1路由器
11
+ v1_router = APIRouter(prefix="/v1")
12
+
13
+ # 注册子路由
14
+ v1_router.include_router(queues_router, prefix="/queues", tags=["queues"])
15
+ v1_router.include_router(tasks_router, prefix="/tasks", tags=["tasks"])
16
+ v1_router.include_router(monitoring_router, prefix="/monitoring", tags=["monitoring"])
17
+ v1_router.include_router(namespaces_router, prefix="/namespaces", tags=["namespaces"])