feat(监控): 实时服务状态界面 - Oracle 新增 service_activity 活动表(只保留7天,写入时清理)与 /api/oracle/activity 接口(队列实时状态/当前处理视频/模型调用/rclone/人物合并/最近50条活动);VideoQueue 打点+当前处理跟踪+status();PersonService 打点;rclone_sync.sh 同步结果写活动表;fam-ui 新增'🖥 服务状态'页(状态卡+活动时间流,直连 Oracle 实时拉取)
This commit is contained in:
@@ -58,6 +58,9 @@ class VideoQueue:
|
||||
self._producer = None
|
||||
self._consumers: List[threading.Thread] = []
|
||||
self._stats = {"produced": 0, "consumed_ok": 0, "consumed_fail": 0}
|
||||
# 实时状态:当前正在处理的视频(服务状态界面用)
|
||||
self._current = None
|
||||
self._current_lock = threading.Lock()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 生产者
|
||||
@@ -97,16 +100,19 @@ class VideoQueue:
|
||||
vid = self.db.ensure_video(fn, path, camera_name=self.camera_name)
|
||||
self.db.set_video_file_status(vid, True, '', vmeta)
|
||||
logger.info(f"登记新视频并入队: {fn} (id={vid}, meta={vmeta})")
|
||||
self.db.record_activity('queue', 'register', f"{fn} (id={vid})")
|
||||
self._enqueue(vid)
|
||||
continue
|
||||
vid = self.db.ensure_video(fn, path, camera_name=self.camera_name)
|
||||
logger.info(f"登记新视频并入队: {fn} (id={vid})")
|
||||
self.db.record_activity('queue', 'register', f"{fn} (id={vid})")
|
||||
self._enqueue(vid)
|
||||
elif row['status'] in ('pending', 'failed') and self._retry_allowed(row):
|
||||
self._enqueue(row['id'])
|
||||
elif row['status'] == 'done' and self._file_changed(row, path):
|
||||
# 文件被覆盖(rclone 重新同步/更新):重置 pending 重新分析
|
||||
logger.info(f"文件内容变更,重置重新分析: {fn} (id={row['id']})")
|
||||
self.db.record_activity('queue', 'reanalyze', f"{fn} (id={row['id']})")
|
||||
self.db._conn.execute(
|
||||
"UPDATE videos SET status='pending', retry_count=0, summary_json=NULL, "
|
||||
"events_json=NULL, people_json=NULL, compute_provider=NULL, "
|
||||
@@ -209,13 +215,38 @@ class VideoQueue:
|
||||
if not self._retry_allowed(row):
|
||||
logger.warning(f"[video_id={video_id}] 已达重试上限({row['retry_count']}),放弃")
|
||||
return
|
||||
ok = processor.process_video(
|
||||
video_id, row['filename'], row['local_path'],
|
||||
timeout_multiplier=self.timeout_multiplier)
|
||||
if ok:
|
||||
self._stats["consumed_ok"] += 1
|
||||
else:
|
||||
self._stats["consumed_fail"] += 1
|
||||
fn = row['filename'] or ''
|
||||
with self._current_lock:
|
||||
self._current = {"video_id": video_id, "filename": fn,
|
||||
"started_at": None}
|
||||
self.db.record_activity('queue', 'process_start', f"video {video_id} {fn}")
|
||||
try:
|
||||
ok = processor.process_video(
|
||||
video_id, fn, row['local_path'],
|
||||
timeout_multiplier=self.timeout_multiplier)
|
||||
if ok:
|
||||
self._stats["consumed_ok"] += 1
|
||||
self.db.record_activity('queue', 'process_done', f"video {video_id} {fn}")
|
||||
else:
|
||||
self._stats["consumed_fail"] += 1
|
||||
self.db.record_activity('queue', 'process_fail', f"video {video_id} {fn}")
|
||||
finally:
|
||||
with self._current_lock:
|
||||
self._current = None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 实时状态(服务状态界面用)
|
||||
# ------------------------------------------------------------------
|
||||
def status(self) -> Dict:
|
||||
with self._current_lock:
|
||||
cur = dict(self._current) if self._current else None
|
||||
return {
|
||||
"queued": self._queue.qsize(),
|
||||
"current": cur,
|
||||
"stats": dict(self._stats),
|
||||
"max_concurrent": self.max_concurrent,
|
||||
"running": (self._producer is not None and self._producer.is_alive()),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 生命周期
|
||||
|
||||
Reference in New Issue
Block a user