matrice-inference 0.1.2__py3-none-any.whl → 0.1.23__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 matrice-inference might be problematic. Click here for more details.
- matrice_inference/__init__.py +40 -23
- matrice_inference/server/__init__.py +17 -11
- matrice_inference/server/model/triton_server.py +1 -3
- matrice_inference/server/server.py +46 -4
- matrice_inference/server/stream/camera_config_monitor.py +221 -0
- matrice_inference/server/stream/consumer_worker.py +490 -141
- matrice_inference/server/stream/frame_cache.py +279 -56
- matrice_inference/server/stream/inference_worker.py +192 -94
- matrice_inference/server/stream/post_processing_worker.py +253 -181
- matrice_inference/server/stream/producer_worker.py +155 -98
- matrice_inference/server/stream/stream_pipeline.py +234 -250
- matrice_inference/tmp/aggregator/analytics.py +1 -1
- matrice_inference/tmp/overall_inference_testing.py +0 -4
- {matrice_inference-0.1.2.dist-info → matrice_inference-0.1.23.dist-info}/METADATA +1 -1
- {matrice_inference-0.1.2.dist-info → matrice_inference-0.1.23.dist-info}/RECORD +18 -17
- {matrice_inference-0.1.2.dist-info → matrice_inference-0.1.23.dist-info}/WHEEL +0 -0
- {matrice_inference-0.1.2.dist-info → matrice_inference-0.1.23.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_inference-0.1.2.dist-info → matrice_inference-0.1.23.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Streaming pipeline using MatriceStream and updated inference interface:
|
|
3
3
|
Direct processing with priority queues, dynamic camera configuration
|
|
4
4
|
|
|
5
5
|
Architecture:
|
|
@@ -18,186 +18,137 @@ Features:
|
|
|
18
18
|
- Non-blocking threading for consumers/producers
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
import asyncio
|
|
22
|
-
import json
|
|
23
|
-
import time
|
|
24
21
|
import logging
|
|
25
|
-
import threading
|
|
26
22
|
import queue
|
|
27
|
-
import signal
|
|
28
|
-
import copy
|
|
29
|
-
from dataclasses import dataclass, field
|
|
30
|
-
from typing import Any, Dict, Optional, List, Union
|
|
31
23
|
from concurrent.futures import ThreadPoolExecutor
|
|
24
|
+
from typing import Any, Dict, List, Optional
|
|
32
25
|
|
|
33
|
-
from matrice_common.stream.matrice_stream import MatriceStream, StreamType
|
|
34
26
|
from matrice_analytics.post_processing.post_processor import PostProcessor
|
|
35
|
-
|
|
36
27
|
from matrice_inference.server.inference_interface import InferenceInterface
|
|
37
|
-
from matrice_inference.server.model.model_manager_wrapper import ModelManagerWrapper
|
|
38
|
-
from matrice_inference.server.stream.utils import CameraConfig, StreamMessage
|
|
39
28
|
from matrice_inference.server.stream.consumer_worker import ConsumerWorker
|
|
40
29
|
from matrice_inference.server.stream.inference_worker import InferenceWorker
|
|
41
30
|
from matrice_inference.server.stream.post_processing_worker import PostProcessingWorker
|
|
42
31
|
from matrice_inference.server.stream.producer_worker import ProducerWorker
|
|
43
|
-
|
|
32
|
+
from matrice_inference.server.stream.utils import CameraConfig
|
|
44
33
|
|
|
45
34
|
|
|
46
35
|
|
|
47
36
|
class StreamingPipeline:
|
|
48
|
-
"""
|
|
49
|
-
|
|
37
|
+
"""Optimized streaming pipeline with dynamic camera configuration and clean resource management."""
|
|
38
|
+
|
|
39
|
+
DEFAULT_QUEUE_SIZE = 5000
|
|
40
|
+
DEFAULT_MESSAGE_TIMEOUT = 10.0
|
|
41
|
+
DEFAULT_INFERENCE_TIMEOUT = 30.0
|
|
42
|
+
DEFAULT_SHUTDOWN_TIMEOUT = 30.0
|
|
43
|
+
|
|
50
44
|
def __init__(
|
|
51
45
|
self,
|
|
52
46
|
inference_interface: InferenceInterface,
|
|
53
47
|
post_processor: PostProcessor,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
output_queue_maxsize=5000,
|
|
61
|
-
message_timeout=10.0,
|
|
62
|
-
inference_timeout=30.0,
|
|
63
|
-
shutdown_timeout=30.0,
|
|
48
|
+
inference_queue_maxsize: int = DEFAULT_QUEUE_SIZE,
|
|
49
|
+
postproc_queue_maxsize: int = DEFAULT_QUEUE_SIZE,
|
|
50
|
+
output_queue_maxsize: int = DEFAULT_QUEUE_SIZE,
|
|
51
|
+
message_timeout: float = DEFAULT_MESSAGE_TIMEOUT,
|
|
52
|
+
inference_timeout: float = DEFAULT_INFERENCE_TIMEOUT,
|
|
53
|
+
shutdown_timeout: float = DEFAULT_SHUTDOWN_TIMEOUT,
|
|
64
54
|
camera_configs: Optional[Dict[str, CameraConfig]] = None,
|
|
65
55
|
):
|
|
66
56
|
self.inference_interface = inference_interface
|
|
67
57
|
self.post_processor = post_processor
|
|
68
|
-
self.consumer_threads = consumer_threads
|
|
69
|
-
self.producer_threads = producer_threads
|
|
70
|
-
self.inference_threads = inference_threads
|
|
71
|
-
self.postprocessing_threads = postprocessing_threads
|
|
72
|
-
self.inference_queue_maxsize = inference_queue_maxsize
|
|
73
|
-
self.postproc_queue_maxsize = postproc_queue_maxsize
|
|
74
|
-
self.output_queue_maxsize = output_queue_maxsize
|
|
75
58
|
self.message_timeout = message_timeout
|
|
76
59
|
self.inference_timeout = inference_timeout
|
|
77
60
|
self.shutdown_timeout = shutdown_timeout
|
|
78
|
-
|
|
79
|
-
# Camera configurations (can be empty initially)
|
|
61
|
+
|
|
80
62
|
self.camera_configs: Dict[str, CameraConfig] = camera_configs or {}
|
|
81
|
-
|
|
82
|
-
# Priority queues for pipeline stages
|
|
83
|
-
self.inference_queue = queue.PriorityQueue(maxsize=self.inference_queue_maxsize)
|
|
84
|
-
self.postproc_queue = queue.PriorityQueue(maxsize=self.postproc_queue_maxsize)
|
|
85
|
-
self.output_queue = queue.PriorityQueue(maxsize=self.output_queue_maxsize)
|
|
86
|
-
|
|
87
|
-
# Thread pools for CPU/GPU intensive work
|
|
88
|
-
self.inference_executor = ThreadPoolExecutor(self.inference_threads)
|
|
89
|
-
self.postprocessing_executor = ThreadPoolExecutor(self.postprocessing_threads)
|
|
90
|
-
|
|
91
|
-
# No centralized stream management - each worker creates its own streams
|
|
92
|
-
|
|
93
|
-
# Worker instances
|
|
94
|
-
self.consumer_workers: Dict[str, List[ConsumerWorker]] = {}
|
|
95
|
-
self.inference_workers = []
|
|
96
|
-
self.postproc_workers = []
|
|
97
|
-
self.producer_workers = []
|
|
98
|
-
|
|
99
|
-
# Worker threads
|
|
100
|
-
self.worker_threads = []
|
|
101
|
-
|
|
102
|
-
# Control state
|
|
103
63
|
self.running = False
|
|
104
|
-
|
|
105
64
|
self.logger = logging.getLogger(__name__)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
65
|
+
|
|
66
|
+
self._setup_queues(inference_queue_maxsize, postproc_queue_maxsize, output_queue_maxsize)
|
|
67
|
+
self._setup_executors()
|
|
68
|
+
self._setup_workers()
|
|
69
|
+
# Frame cache instance (initialized lazily at start)
|
|
70
|
+
self.frame_cache = None
|
|
71
|
+
|
|
72
|
+
def _setup_queues(self, inference_size: int, postproc_size: int, output_size: int) -> None:
|
|
73
|
+
"""Initialize priority queues for pipeline stages."""
|
|
74
|
+
self.inference_queue = queue.PriorityQueue(maxsize=inference_size)
|
|
75
|
+
self.postproc_queue = queue.PriorityQueue(maxsize=postproc_size)
|
|
76
|
+
self.output_queue = queue.PriorityQueue(maxsize=output_size)
|
|
77
|
+
|
|
78
|
+
def _setup_executors(self) -> None:
|
|
79
|
+
"""Initialize thread pool executors."""
|
|
80
|
+
# Single-thread executors to preserve strict ordering
|
|
81
|
+
self.inference_executor = ThreadPoolExecutor(max_workers=1)
|
|
82
|
+
self.postprocessing_executor = ThreadPoolExecutor(max_workers=1)
|
|
83
|
+
|
|
84
|
+
def _setup_workers(self) -> None:
|
|
85
|
+
"""Initialize worker containers."""
|
|
86
|
+
self.consumer_workers: Dict[str, List[ConsumerWorker]] = {}
|
|
87
|
+
self.inference_workers: List = []
|
|
88
|
+
self.postproc_workers: List = []
|
|
89
|
+
self.producer_workers: List = []
|
|
90
|
+
self.worker_threads: List = []
|
|
112
91
|
|
|
113
|
-
async def start(self):
|
|
114
|
-
"""Start the pipeline."""
|
|
92
|
+
async def start(self) -> None:
|
|
93
|
+
"""Start the pipeline with proper error handling."""
|
|
115
94
|
if self.running:
|
|
116
95
|
self.logger.warning("Pipeline already running")
|
|
117
96
|
return
|
|
118
|
-
|
|
97
|
+
|
|
119
98
|
self.running = True
|
|
120
|
-
self.logger.info("Starting
|
|
121
|
-
|
|
99
|
+
self.logger.info("Starting streaming pipeline...")
|
|
100
|
+
|
|
122
101
|
try:
|
|
123
|
-
#
|
|
124
|
-
|
|
125
|
-
# if not fc.get("host"):
|
|
126
|
-
# for cfg in self.camera_configs.values():
|
|
127
|
-
# sc = cfg.stream_config
|
|
128
|
-
# if sc.get("stream_type", "kafka").lower() == "redis":
|
|
129
|
-
# fc.setdefault("host", sc.get("host", "localhost"))
|
|
130
|
-
# fc.setdefault("port", sc.get("port", 6379))
|
|
131
|
-
# fc.setdefault("password", sc.get("password"))
|
|
132
|
-
# fc.setdefault("username", sc.get("username"))
|
|
133
|
-
# fc.setdefault("db", sc.get("db", 0))
|
|
134
|
-
# break
|
|
135
|
-
# try:
|
|
136
|
-
# self.frame_cache = RedisFrameCache(
|
|
137
|
-
# host=fc.get("host", "localhost"),
|
|
138
|
-
# port=fc.get("port", 6379),
|
|
139
|
-
# db=fc.get("db", 0),
|
|
140
|
-
# password=fc.get("password"),
|
|
141
|
-
# username=fc.get("username"),
|
|
142
|
-
# ttl_seconds=fc.get("ttl_seconds", 300),
|
|
143
|
-
# prefix=fc.get("prefix", "stream:frames:"),
|
|
144
|
-
# )
|
|
145
|
-
# self.frame_cache.start()
|
|
146
|
-
# except Exception as _:
|
|
147
|
-
# self.frame_cache = None
|
|
148
|
-
# self.logger.warning("Frame cache initialization failed; proceeding without cache")
|
|
149
|
-
|
|
150
|
-
# Initialize streams for existing camera configs
|
|
151
|
-
await self._initialize_streams()
|
|
152
|
-
|
|
153
|
-
# Create and start workers
|
|
102
|
+
# Initialize frame cache before workers
|
|
103
|
+
self._initialize_frame_cache()
|
|
154
104
|
await self._create_workers()
|
|
155
105
|
self._start_workers()
|
|
156
|
-
|
|
157
|
-
self.logger.info(
|
|
158
|
-
f"Pipeline started with {len(self.camera_configs)} cameras, "
|
|
159
|
-
f"{sum(len(workers) for workers in self.consumer_workers.values())} consumer workers, "
|
|
160
|
-
f"{len(self.inference_workers)} inference workers, "
|
|
161
|
-
f"{len(self.postproc_workers)} post-processing workers, "
|
|
162
|
-
f"{len(self.producer_workers)} producer workers"
|
|
163
|
-
)
|
|
164
|
-
|
|
106
|
+
self._log_startup_info()
|
|
165
107
|
except Exception as e:
|
|
166
108
|
self.logger.error(f"Failed to start pipeline: {e}")
|
|
167
109
|
await self.stop()
|
|
168
110
|
raise
|
|
111
|
+
|
|
112
|
+
def _log_startup_info(self) -> None:
|
|
113
|
+
"""Log pipeline startup information."""
|
|
114
|
+
consumer_count = sum(len(workers) for workers in self.consumer_workers.values())
|
|
115
|
+
self.logger.info(
|
|
116
|
+
f"Pipeline started - Cameras: {len(self.camera_configs)}, "
|
|
117
|
+
f"Consumers: {consumer_count}, Inference: {len(self.inference_workers)}, "
|
|
118
|
+
f"PostProc: {len(self.postproc_workers)}, Producers: {len(self.producer_workers)}"
|
|
119
|
+
)
|
|
169
120
|
|
|
170
|
-
async def stop(self):
|
|
171
|
-
"""Stop the pipeline gracefully."""
|
|
121
|
+
async def stop(self) -> None:
|
|
122
|
+
"""Stop the pipeline gracefully with proper cleanup."""
|
|
172
123
|
if not self.running:
|
|
173
124
|
return
|
|
174
|
-
|
|
125
|
+
|
|
175
126
|
self.logger.info("Stopping pipeline...")
|
|
176
127
|
self.running = False
|
|
177
|
-
|
|
178
|
-
# Stop all workers
|
|
128
|
+
|
|
179
129
|
self._stop_workers()
|
|
180
|
-
|
|
181
|
-
|
|
130
|
+
self._wait_for_threads()
|
|
131
|
+
self._shutdown_executors()
|
|
132
|
+
|
|
133
|
+
# Stop frame cache if running
|
|
134
|
+
try:
|
|
135
|
+
if self.frame_cache:
|
|
136
|
+
self.frame_cache.stop()
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
self.logger.info("Pipeline stopped")
|
|
141
|
+
|
|
142
|
+
def _wait_for_threads(self) -> None:
|
|
143
|
+
"""Wait for all worker threads to complete."""
|
|
182
144
|
for thread in self.worker_threads:
|
|
183
145
|
if thread.is_alive():
|
|
184
146
|
thread.join(timeout=self.shutdown_timeout)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# Shutdown executors
|
|
147
|
+
|
|
148
|
+
def _shutdown_executors(self) -> None:
|
|
149
|
+
"""Shutdown thread pool executors."""
|
|
190
150
|
self.inference_executor.shutdown(wait=False)
|
|
191
151
|
self.postprocessing_executor.shutdown(wait=False)
|
|
192
|
-
|
|
193
|
-
# Frame cache stop disabled
|
|
194
|
-
# try:
|
|
195
|
-
# if self.frame_cache:
|
|
196
|
-
# self.frame_cache.stop()
|
|
197
|
-
# except Exception:
|
|
198
|
-
# pass
|
|
199
|
-
|
|
200
|
-
self.logger.info("Pipeline stopped")
|
|
201
152
|
|
|
202
153
|
async def add_camera_config(self, camera_config: CameraConfig) -> bool:
|
|
203
154
|
"""
|
|
@@ -273,149 +224,137 @@ class StreamingPipeline:
|
|
|
273
224
|
return await self.add_camera_config(camera_config)
|
|
274
225
|
|
|
275
226
|
def enable_camera(self, camera_id: str) -> bool:
|
|
276
|
-
"""Enable a camera."""
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
self.logger.info(f"Camera {camera_id} enabled")
|
|
280
|
-
return True
|
|
281
|
-
return False
|
|
282
|
-
|
|
227
|
+
"""Enable a camera configuration."""
|
|
228
|
+
return self._set_camera_state(camera_id, True, "enabled")
|
|
229
|
+
|
|
283
230
|
def disable_camera(self, camera_id: str) -> bool:
|
|
284
|
-
"""Disable a camera."""
|
|
231
|
+
"""Disable a camera configuration."""
|
|
232
|
+
return self._set_camera_state(camera_id, False, "disabled")
|
|
233
|
+
|
|
234
|
+
def _set_camera_state(self, camera_id: str, enabled: bool, state_name: str) -> bool:
|
|
235
|
+
"""Set camera enabled state."""
|
|
285
236
|
if camera_id in self.camera_configs:
|
|
286
|
-
self.camera_configs[camera_id].enabled =
|
|
287
|
-
self.logger.info(f"Camera {camera_id}
|
|
237
|
+
self.camera_configs[camera_id].enabled = enabled
|
|
238
|
+
self.logger.info(f"Camera {camera_id} {state_name}")
|
|
288
239
|
return True
|
|
289
240
|
return False
|
|
241
|
+
|
|
290
242
|
|
|
291
|
-
async def
|
|
292
|
-
"""
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
"""No centralized streams to close - workers manage their own streams."""
|
|
301
|
-
pass
|
|
302
|
-
|
|
303
|
-
async def _close_camera_streams(self, camera_id: str):
|
|
304
|
-
"""No centralized camera streams to close - workers manage their own streams."""
|
|
305
|
-
pass
|
|
306
|
-
|
|
307
|
-
async def _create_workers(self):
|
|
308
|
-
"""Create all worker instances."""
|
|
309
|
-
# Create consumer workers (per camera)
|
|
243
|
+
async def _create_workers(self) -> None:
|
|
244
|
+
"""Create all worker instances for the pipeline."""
|
|
245
|
+
await self._create_consumer_workers()
|
|
246
|
+
self._create_inference_worker()
|
|
247
|
+
self._create_postprocessing_worker()
|
|
248
|
+
self._create_producer_worker()
|
|
249
|
+
|
|
250
|
+
async def _create_consumer_workers(self) -> None:
|
|
251
|
+
"""Create consumer workers for all cameras."""
|
|
310
252
|
for camera_config in self.camera_configs.values():
|
|
311
253
|
await self._create_camera_workers(camera_config)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
254
|
+
|
|
255
|
+
def _create_inference_worker(self) -> None:
|
|
256
|
+
"""Create single inference worker."""
|
|
257
|
+
worker = InferenceWorker(
|
|
258
|
+
worker_id=0,
|
|
259
|
+
inference_queue=self.inference_queue,
|
|
260
|
+
postproc_queue=self.postproc_queue,
|
|
261
|
+
inference_executor=self.inference_executor,
|
|
262
|
+
message_timeout=self.message_timeout,
|
|
263
|
+
inference_timeout=self.inference_timeout,
|
|
264
|
+
inference_interface=self.inference_interface
|
|
265
|
+
)
|
|
266
|
+
self.inference_workers.append(worker)
|
|
267
|
+
|
|
268
|
+
def _create_postprocessing_worker(self) -> None:
|
|
269
|
+
"""Create single post-processing worker."""
|
|
270
|
+
worker = PostProcessingWorker(
|
|
271
|
+
worker_id=0,
|
|
272
|
+
postproc_queue=self.postproc_queue,
|
|
273
|
+
output_queue=self.output_queue,
|
|
274
|
+
postprocessing_executor=self.postprocessing_executor,
|
|
275
|
+
message_timeout=self.message_timeout,
|
|
276
|
+
inference_timeout=self.inference_timeout,
|
|
277
|
+
post_processor=self.post_processor,
|
|
278
|
+
frame_cache=self.frame_cache,
|
|
279
|
+
)
|
|
280
|
+
self.postproc_workers.append(worker)
|
|
281
|
+
|
|
282
|
+
def _create_producer_worker(self) -> None:
|
|
283
|
+
"""Create single producer worker."""
|
|
284
|
+
worker = ProducerWorker(
|
|
285
|
+
worker_id=0,
|
|
286
|
+
output_queue=self.output_queue,
|
|
287
|
+
camera_configs=self.camera_configs,
|
|
288
|
+
message_timeout=self.message_timeout
|
|
289
|
+
)
|
|
290
|
+
self.producer_workers.append(worker)
|
|
349
291
|
|
|
350
|
-
async def _create_camera_workers(self, camera_config: CameraConfig):
|
|
292
|
+
async def _create_camera_workers(self, camera_config: CameraConfig) -> None:
|
|
351
293
|
"""Create consumer workers for a specific camera."""
|
|
352
294
|
camera_id = camera_config.camera_id
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
camera_workers.append(worker)
|
|
367
|
-
|
|
368
|
-
self.consumer_workers[camera_id] = camera_workers
|
|
295
|
+
|
|
296
|
+
worker = ConsumerWorker(
|
|
297
|
+
camera_id=camera_id,
|
|
298
|
+
worker_id=0,
|
|
299
|
+
stream_config=camera_config.stream_config,
|
|
300
|
+
input_topic=camera_config.input_topic,
|
|
301
|
+
inference_queue=self.inference_queue,
|
|
302
|
+
message_timeout=self.message_timeout,
|
|
303
|
+
camera_config=camera_config,
|
|
304
|
+
frame_cache=self.frame_cache
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
self.consumer_workers[camera_id] = [worker]
|
|
369
308
|
|
|
370
|
-
def _start_workers(self):
|
|
371
|
-
"""Start all worker instances."""
|
|
372
|
-
|
|
309
|
+
def _start_workers(self) -> None:
|
|
310
|
+
"""Start all worker instances and track their threads."""
|
|
311
|
+
self._start_all_camera_workers()
|
|
312
|
+
self._start_worker_group(self.inference_workers)
|
|
313
|
+
self._start_worker_group(self.postproc_workers)
|
|
314
|
+
self._start_worker_group(self.producer_workers)
|
|
315
|
+
|
|
316
|
+
def _start_all_camera_workers(self) -> None:
|
|
317
|
+
"""Start consumer workers for all cameras."""
|
|
373
318
|
for camera_id in self.consumer_workers:
|
|
374
319
|
self._start_camera_workers(camera_id)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
self.worker_threads.append(thread)
|
|
380
|
-
|
|
381
|
-
# Start post-processing workers
|
|
382
|
-
for worker in self.postproc_workers:
|
|
383
|
-
thread = worker.start()
|
|
384
|
-
self.worker_threads.append(thread)
|
|
385
|
-
|
|
386
|
-
# Start producer workers
|
|
387
|
-
for worker in self.producer_workers:
|
|
320
|
+
|
|
321
|
+
def _start_worker_group(self, workers: List) -> None:
|
|
322
|
+
"""Start a group of workers and track their threads."""
|
|
323
|
+
for worker in workers:
|
|
388
324
|
thread = worker.start()
|
|
389
325
|
self.worker_threads.append(thread)
|
|
390
326
|
|
|
391
|
-
def _start_camera_workers(self, camera_id: str):
|
|
327
|
+
def _start_camera_workers(self, camera_id: str) -> None:
|
|
392
328
|
"""Start consumer workers for a specific camera."""
|
|
393
329
|
if camera_id in self.consumer_workers:
|
|
394
|
-
|
|
395
|
-
thread = worker.start()
|
|
396
|
-
self.worker_threads.append(thread)
|
|
330
|
+
self._start_worker_group(self.consumer_workers[camera_id])
|
|
397
331
|
|
|
398
|
-
def _stop_workers(self):
|
|
399
|
-
"""Stop all worker instances."""
|
|
400
|
-
|
|
332
|
+
def _stop_workers(self) -> None:
|
|
333
|
+
"""Stop all worker instances gracefully."""
|
|
334
|
+
self._stop_all_camera_workers()
|
|
335
|
+
self._stop_worker_group(self.inference_workers)
|
|
336
|
+
self._stop_worker_group(self.postproc_workers)
|
|
337
|
+
self._stop_worker_group(self.producer_workers)
|
|
338
|
+
|
|
339
|
+
def _stop_all_camera_workers(self) -> None:
|
|
340
|
+
"""Stop all camera consumer workers."""
|
|
401
341
|
for workers in self.consumer_workers.values():
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
342
|
+
self._stop_worker_group(workers)
|
|
343
|
+
|
|
344
|
+
def _stop_worker_group(self, workers: List) -> None:
|
|
345
|
+
"""Stop a group of workers."""
|
|
346
|
+
for worker in workers:
|
|
406
347
|
worker.stop()
|
|
407
348
|
|
|
408
|
-
async def _stop_camera_workers(self, camera_id: str):
|
|
349
|
+
async def _stop_camera_workers(self, camera_id: str) -> None:
|
|
409
350
|
"""Stop consumer workers for a specific camera."""
|
|
410
351
|
if camera_id in self.consumer_workers:
|
|
411
|
-
|
|
412
|
-
worker.stop()
|
|
413
|
-
# Remove from tracking
|
|
352
|
+
self._stop_worker_group(self.consumer_workers[camera_id])
|
|
414
353
|
del self.consumer_workers[camera_id]
|
|
415
354
|
|
|
416
355
|
def get_metrics(self) -> Dict[str, Any]:
|
|
417
|
-
"""Get pipeline metrics."""
|
|
418
|
-
|
|
356
|
+
"""Get pipeline metrics including frame cache statistics."""
|
|
357
|
+
metrics = {
|
|
419
358
|
"running": self.running,
|
|
420
359
|
"camera_count": len(self.camera_configs),
|
|
421
360
|
"enabled_cameras": sum(1 for config in self.camera_configs.values() if config.enabled),
|
|
@@ -434,10 +373,6 @@ class StreamingPipeline:
|
|
|
434
373
|
"total_threads": len(self.worker_threads),
|
|
435
374
|
"active_threads": len([t for t in self.worker_threads if t.is_alive()]),
|
|
436
375
|
},
|
|
437
|
-
"thread_pool_sizes": {
|
|
438
|
-
"inference_threads": self.inference_threads,
|
|
439
|
-
"postprocessing_threads": self.postprocessing_threads,
|
|
440
|
-
},
|
|
441
376
|
"camera_configs": {
|
|
442
377
|
camera_id: {
|
|
443
378
|
"input_topic": config.input_topic,
|
|
@@ -448,4 +383,53 @@ class StreamingPipeline:
|
|
|
448
383
|
for camera_id, config in self.camera_configs.items()
|
|
449
384
|
}
|
|
450
385
|
}
|
|
386
|
+
|
|
387
|
+
# Add frame cache metrics if available
|
|
388
|
+
if self.frame_cache:
|
|
389
|
+
try:
|
|
390
|
+
metrics["frame_cache"] = self.frame_cache.get_metrics()
|
|
391
|
+
except Exception as e:
|
|
392
|
+
self.logger.warning(f"Failed to get frame cache metrics: {e}")
|
|
393
|
+
metrics["frame_cache"] = {"error": str(e)}
|
|
394
|
+
else:
|
|
395
|
+
metrics["frame_cache"] = {"enabled": False}
|
|
396
|
+
|
|
397
|
+
return metrics
|
|
451
398
|
|
|
399
|
+
def _initialize_frame_cache(self) -> None:
|
|
400
|
+
"""Initialize RedisFrameCache with TTL 10 minutes, deriving connection from Redis cameras if available."""
|
|
401
|
+
try:
|
|
402
|
+
# Find a Redis camera config for connection params
|
|
403
|
+
host = "localhost"
|
|
404
|
+
port = 6379
|
|
405
|
+
password = None
|
|
406
|
+
username = None
|
|
407
|
+
db = 0
|
|
408
|
+
|
|
409
|
+
for cfg in self.camera_configs.values():
|
|
410
|
+
sc = cfg.stream_config or {}
|
|
411
|
+
st = sc.get("stream_type", "kafka").lower()
|
|
412
|
+
if st == "redis":
|
|
413
|
+
host = sc.get("host", host)
|
|
414
|
+
port = sc.get("port", port)
|
|
415
|
+
password = sc.get("password", password)
|
|
416
|
+
username = sc.get("username", username)
|
|
417
|
+
db = sc.get("db", db)
|
|
418
|
+
break
|
|
419
|
+
|
|
420
|
+
# Lazy import to avoid dependency issues if not used
|
|
421
|
+
from matrice_inference.server.stream.frame_cache import RedisFrameCache
|
|
422
|
+
self.frame_cache = RedisFrameCache(
|
|
423
|
+
host=host,
|
|
424
|
+
port=port,
|
|
425
|
+
db=db,
|
|
426
|
+
password=password,
|
|
427
|
+
username=username,
|
|
428
|
+
ttl_seconds=600, # 10 minutes
|
|
429
|
+
worker_threads=1, # conservative to avoid contention
|
|
430
|
+
)
|
|
431
|
+
self.frame_cache.start()
|
|
432
|
+
self.logger.info("Initialized RedisFrameCache with 10-minute TTL")
|
|
433
|
+
except Exception as e:
|
|
434
|
+
self.frame_cache = None
|
|
435
|
+
self.logger.warning(f"Frame cache initialization failed; proceeding without cache: {e}")
|
|
@@ -73,7 +73,7 @@ class AnalyticsSummarizer:
|
|
|
73
73
|
# application_key_name: {
|
|
74
74
|
# "meta": {name, key_name, version},
|
|
75
75
|
# "last_input_timestamp": str,
|
|
76
|
-
# "earliest_reset_timestamp": str
|
|
76
|
+
# "earliest_reset_timestamp": str or None,
|
|
77
77
|
# "current_counts": {category: last_value},
|
|
78
78
|
# "total_counts": {category: max_value}
|
|
79
79
|
# }
|