jettask 0.2.4__py3-none-any.whl → 0.2.6__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.
- jettask/core/cli.py +20 -24
- jettask/monitor/run_backlog_collector.py +96 -0
- jettask/monitor/stream_backlog_monitor.py +362 -0
- jettask/pg_consumer/pg_consumer_v2.py +403 -0
- jettask/pg_consumer/sql_utils.py +182 -0
- jettask/scheduler/__init__.py +17 -0
- jettask/scheduler/add_execution_count.sql +11 -0
- jettask/scheduler/add_priority_field.sql +26 -0
- jettask/scheduler/add_scheduler_id.sql +25 -0
- jettask/scheduler/add_scheduler_id_index.sql +10 -0
- jettask/scheduler/loader.py +249 -0
- jettask/scheduler/make_scheduler_id_required.sql +28 -0
- jettask/scheduler/manager.py +696 -0
- jettask/scheduler/migrate_interval_seconds.sql +9 -0
- jettask/scheduler/models.py +200 -0
- jettask/scheduler/multi_namespace_scheduler.py +294 -0
- jettask/scheduler/performance_optimization.sql +45 -0
- jettask/scheduler/run_scheduler.py +186 -0
- jettask/scheduler/scheduler.py +715 -0
- jettask/scheduler/schema.sql +84 -0
- jettask/scheduler/unified_manager.py +450 -0
- jettask/scheduler/unified_scheduler_manager.py +280 -0
- jettask/webui/backend/api/__init__.py +3 -0
- jettask/webui/backend/api/v1/__init__.py +17 -0
- jettask/webui/backend/api/v1/monitoring.py +431 -0
- jettask/webui/backend/api/v1/namespaces.py +504 -0
- jettask/webui/backend/api/v1/queues.py +342 -0
- jettask/webui/backend/api/v1/tasks.py +367 -0
- jettask/webui/backend/core/__init__.py +3 -0
- jettask/webui/backend/core/cache.py +221 -0
- jettask/webui/backend/core/database.py +200 -0
- jettask/webui/backend/core/exceptions.py +102 -0
- jettask/webui/backend/models/__init__.py +3 -0
- jettask/webui/backend/models/requests.py +236 -0
- jettask/webui/backend/models/responses.py +230 -0
- jettask/webui/backend/services/__init__.py +3 -0
- jettask/webui/frontend/index.html +13 -0
- jettask/webui/models/__init__.py +3 -0
- jettask/webui/models/namespace.py +63 -0
- jettask/webui/sql/batch_upsert_functions.sql +178 -0
- jettask/webui/sql/init_database.sql +640 -0
- {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/METADATA +11 -9
- {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/RECORD +47 -54
- jettask/webui/frontend/package-lock.json +0 -4833
- jettask/webui/frontend/package.json +0 -30
- jettask/webui/frontend/src/App.css +0 -109
- jettask/webui/frontend/src/App.jsx +0 -66
- jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -166
- jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -298
- jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -638
- jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -65
- jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -487
- jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -465
- jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -423
- jettask/webui/frontend/src/components/TaskFilter.jsx +0 -425
- jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -21
- jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -160
- jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -111
- jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -115
- jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -40
- jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -18
- jettask/webui/frontend/src/components/layout/AppLayout.css +0 -95
- jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -49
- jettask/webui/frontend/src/components/layout/Header.css +0 -106
- jettask/webui/frontend/src/components/layout/Header.jsx +0 -106
- jettask/webui/frontend/src/components/layout/SideMenu.css +0 -137
- jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -209
- jettask/webui/frontend/src/components/layout/TabsNav.css +0 -244
- jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -206
- jettask/webui/frontend/src/components/layout/UserInfo.css +0 -197
- jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -197
- jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -27
- jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -72
- jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -245
- jettask/webui/frontend/src/index.css +0 -114
- jettask/webui/frontend/src/main.jsx +0 -20
- jettask/webui/frontend/src/pages/Alerts.jsx +0 -684
- jettask/webui/frontend/src/pages/Dashboard/index.css +0 -35
- jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -281
- jettask/webui/frontend/src/pages/Dashboard.jsx +0 -1330
- jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -1117
- jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -527
- jettask/webui/frontend/src/pages/Queues.jsx +0 -12
- jettask/webui/frontend/src/pages/ScheduledTasks.jsx +0 -809
- jettask/webui/frontend/src/pages/Settings.jsx +0 -800
- jettask/webui/frontend/src/pages/Workers.jsx +0 -12
- jettask/webui/frontend/src/services/api.js +0 -114
- jettask/webui/frontend/src/services/queueTrend.js +0 -152
- jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
- jettask/webui/frontend/src/utils/userPreferences.js +0 -154
- {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/WHEEL +0 -0
- {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.4.dist-info → jettask-0.2.6.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,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"])
|