matrice-inference 0.1.2__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 +72 -0
- matrice_inference/py.typed +0 -0
- matrice_inference/server/__init__.py +23 -0
- matrice_inference/server/inference_interface.py +176 -0
- matrice_inference/server/model/__init__.py +1 -0
- matrice_inference/server/model/model_manager.py +274 -0
- matrice_inference/server/model/model_manager_wrapper.py +550 -0
- matrice_inference/server/model/triton_model_manager.py +290 -0
- matrice_inference/server/model/triton_server.py +1248 -0
- matrice_inference/server/proxy_interface.py +371 -0
- matrice_inference/server/server.py +1004 -0
- matrice_inference/server/stream/__init__.py +0 -0
- matrice_inference/server/stream/app_deployment.py +228 -0
- matrice_inference/server/stream/consumer_worker.py +201 -0
- matrice_inference/server/stream/frame_cache.py +127 -0
- matrice_inference/server/stream/inference_worker.py +163 -0
- matrice_inference/server/stream/post_processing_worker.py +230 -0
- matrice_inference/server/stream/producer_worker.py +147 -0
- matrice_inference/server/stream/stream_pipeline.py +451 -0
- matrice_inference/server/stream/utils.py +23 -0
- matrice_inference/tmp/abstract_model_manager.py +58 -0
- matrice_inference/tmp/aggregator/__init__.py +18 -0
- matrice_inference/tmp/aggregator/aggregator.py +330 -0
- matrice_inference/tmp/aggregator/analytics.py +906 -0
- matrice_inference/tmp/aggregator/ingestor.py +438 -0
- matrice_inference/tmp/aggregator/latency.py +597 -0
- matrice_inference/tmp/aggregator/pipeline.py +968 -0
- matrice_inference/tmp/aggregator/publisher.py +431 -0
- matrice_inference/tmp/aggregator/synchronizer.py +594 -0
- matrice_inference/tmp/batch_manager.py +239 -0
- matrice_inference/tmp/overall_inference_testing.py +338 -0
- matrice_inference/tmp/triton_utils.py +638 -0
- matrice_inference-0.1.2.dist-info/METADATA +28 -0
- matrice_inference-0.1.2.dist-info/RECORD +37 -0
- matrice_inference-0.1.2.dist-info/WHEEL +5 -0
- matrice_inference-0.1.2.dist-info/licenses/LICENSE.txt +21 -0
- matrice_inference-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
import queue
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
9
|
+
|
|
10
|
+
class InferenceWorker:
|
|
11
|
+
"""Handles inference processing using threading."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, worker_id: int, inference_queue: queue.PriorityQueue,
|
|
14
|
+
postproc_queue: queue.PriorityQueue, inference_executor: ThreadPoolExecutor,
|
|
15
|
+
message_timeout: float, inference_timeout: float, inference_interface=None):
|
|
16
|
+
self.worker_id = worker_id
|
|
17
|
+
self.inference_queue = inference_queue
|
|
18
|
+
self.postproc_queue = postproc_queue
|
|
19
|
+
self.inference_executor = inference_executor
|
|
20
|
+
self.message_timeout = message_timeout
|
|
21
|
+
self.inference_timeout = inference_timeout
|
|
22
|
+
self.inference_interface = inference_interface
|
|
23
|
+
self.running = False
|
|
24
|
+
self.logger = logging.getLogger(f"{__name__}.inference.{worker_id}")
|
|
25
|
+
|
|
26
|
+
def start(self):
|
|
27
|
+
"""Start the inference worker in a separate thread."""
|
|
28
|
+
self.running = True
|
|
29
|
+
thread = threading.Thread(target=self._run, name=f"InferenceWorker-{self.worker_id}", daemon=False)
|
|
30
|
+
thread.start()
|
|
31
|
+
return thread
|
|
32
|
+
|
|
33
|
+
def stop(self):
|
|
34
|
+
"""Stop the inference worker."""
|
|
35
|
+
self.running = False
|
|
36
|
+
|
|
37
|
+
def _run(self):
|
|
38
|
+
"""Main inference dispatcher loop."""
|
|
39
|
+
self.logger.info(f"Started inference worker {self.worker_id}")
|
|
40
|
+
|
|
41
|
+
while self.running:
|
|
42
|
+
try:
|
|
43
|
+
# Get task from inference queue
|
|
44
|
+
try:
|
|
45
|
+
priority, timestamp, task_data = self.inference_queue.get(timeout=self.message_timeout)
|
|
46
|
+
except queue.Empty:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
# Process inference task
|
|
50
|
+
self._process_inference_task(priority, task_data)
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
self.logger.error(f"Inference worker error: {e}")
|
|
54
|
+
|
|
55
|
+
self.logger.info(f"Inference worker {self.worker_id} stopped")
|
|
56
|
+
|
|
57
|
+
def _process_inference_task(self, priority: int, task_data: Dict[str, Any]):
|
|
58
|
+
"""Process a single inference task."""
|
|
59
|
+
try:
|
|
60
|
+
message = task_data["message"]
|
|
61
|
+
|
|
62
|
+
# Submit to thread pool for async execution
|
|
63
|
+
start_time = time.time()
|
|
64
|
+
future = self.inference_executor.submit(self._run_inference, task_data)
|
|
65
|
+
result = future.result(timeout=self.inference_timeout)
|
|
66
|
+
processing_time = time.time() - start_time
|
|
67
|
+
|
|
68
|
+
if result["success"]:
|
|
69
|
+
# Create post-processing task
|
|
70
|
+
postproc_task = {
|
|
71
|
+
"original_message": message,
|
|
72
|
+
"model_result": result["model_result"],
|
|
73
|
+
"metadata": result["metadata"],
|
|
74
|
+
"processing_time": processing_time,
|
|
75
|
+
"input_stream": task_data["input_stream"],
|
|
76
|
+
"stream_key": task_data["stream_key"],
|
|
77
|
+
"camera_config": task_data["camera_config"]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Add to post-processing queue with timestamp as tie-breaker
|
|
81
|
+
self.postproc_queue.put((priority, time.time(), postproc_task))
|
|
82
|
+
else:
|
|
83
|
+
self.logger.error(f"Inference failed: {result['error']}")
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
self.logger.error(f"Inference task error: {e}")
|
|
87
|
+
|
|
88
|
+
def _run_inference(self, task_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
89
|
+
"""Run inference in thread pool."""
|
|
90
|
+
try:
|
|
91
|
+
# Extract task data - handle camera streamer format
|
|
92
|
+
input_stream_data = task_data.get("input_stream", {})
|
|
93
|
+
input_content = input_stream_data.get("content")
|
|
94
|
+
|
|
95
|
+
# Handle base64 encoded content from camera streamer
|
|
96
|
+
if input_content and isinstance(input_content, str):
|
|
97
|
+
import base64
|
|
98
|
+
try:
|
|
99
|
+
input_content = base64.b64decode(input_content)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logging.warning(f"Failed to decode base64 input: {str(e)}")
|
|
102
|
+
|
|
103
|
+
stream_key = task_data.get("stream_key")
|
|
104
|
+
stream_info = input_stream_data.get("stream_info", {})
|
|
105
|
+
camera_info = input_stream_data.get("camera_info", {})
|
|
106
|
+
extra_params = task_data.get("extra_params", {})
|
|
107
|
+
|
|
108
|
+
# Ensure extra_params is a dictionary
|
|
109
|
+
if not isinstance(extra_params, dict):
|
|
110
|
+
logging.warning(f"extra_params is not a dict in inference worker, converting from {type(extra_params)}: {extra_params}")
|
|
111
|
+
if isinstance(extra_params, list):
|
|
112
|
+
# Convert list to dict if possible
|
|
113
|
+
if len(extra_params) == 0:
|
|
114
|
+
extra_params = {}
|
|
115
|
+
elif all(isinstance(item, dict) for item in extra_params):
|
|
116
|
+
# Merge all dictionaries in the list
|
|
117
|
+
merged_params = {}
|
|
118
|
+
for item in extra_params:
|
|
119
|
+
merged_params.update(item)
|
|
120
|
+
extra_params = merged_params
|
|
121
|
+
else:
|
|
122
|
+
extra_params = {}
|
|
123
|
+
else:
|
|
124
|
+
extra_params = {}
|
|
125
|
+
|
|
126
|
+
if self.inference_interface is None:
|
|
127
|
+
raise ValueError("Inference interface not initialized")
|
|
128
|
+
|
|
129
|
+
# Create event loop for this thread if it doesn't exist
|
|
130
|
+
try:
|
|
131
|
+
loop = asyncio.get_event_loop()
|
|
132
|
+
except RuntimeError:
|
|
133
|
+
loop = asyncio.new_event_loop()
|
|
134
|
+
asyncio.set_event_loop(loop)
|
|
135
|
+
|
|
136
|
+
# Perform inference
|
|
137
|
+
model_result, metadata = loop.run_until_complete(
|
|
138
|
+
self.inference_interface.inference(
|
|
139
|
+
input=input_content,
|
|
140
|
+
extra_params=extra_params,
|
|
141
|
+
apply_post_processing=False, # Inference only
|
|
142
|
+
stream_key=stream_key,
|
|
143
|
+
stream_info=stream_info,
|
|
144
|
+
camera_info=camera_info
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"model_result": model_result,
|
|
150
|
+
"metadata": metadata,
|
|
151
|
+
"success": True,
|
|
152
|
+
"error": None
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logging.error(f"Inference worker error: {str(e)}", exc_info=True)
|
|
157
|
+
return {
|
|
158
|
+
"model_result": None,
|
|
159
|
+
"metadata": None,
|
|
160
|
+
"success": False,
|
|
161
|
+
"error": str(e)
|
|
162
|
+
}
|
|
163
|
+
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
import queue
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PostProcessingWorker:
|
|
11
|
+
"""Handles post-processing using threading."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, worker_id: int, postproc_queue: queue.PriorityQueue,
|
|
14
|
+
output_queue: queue.PriorityQueue, postprocessing_executor: ThreadPoolExecutor,
|
|
15
|
+
message_timeout: float, inference_timeout: float, post_processor=None,
|
|
16
|
+
frame_cache=None):
|
|
17
|
+
self.worker_id = worker_id
|
|
18
|
+
self.postproc_queue = postproc_queue
|
|
19
|
+
self.output_queue = output_queue
|
|
20
|
+
self.postprocessing_executor = postprocessing_executor
|
|
21
|
+
self.message_timeout = message_timeout
|
|
22
|
+
self.inference_timeout = inference_timeout
|
|
23
|
+
self.post_processor = post_processor
|
|
24
|
+
self.frame_cache = frame_cache
|
|
25
|
+
self.running = False
|
|
26
|
+
self.logger = logging.getLogger(f"{__name__}.postproc.{worker_id}")
|
|
27
|
+
|
|
28
|
+
def start(self):
|
|
29
|
+
"""Start the post-processing worker in a separate thread."""
|
|
30
|
+
self.running = True
|
|
31
|
+
thread = threading.Thread(target=self._run, name=f"PostProcWorker-{self.worker_id}", daemon=False)
|
|
32
|
+
thread.start()
|
|
33
|
+
return thread
|
|
34
|
+
|
|
35
|
+
def stop(self):
|
|
36
|
+
"""Stop the post-processing worker."""
|
|
37
|
+
self.running = False
|
|
38
|
+
|
|
39
|
+
def _run(self):
|
|
40
|
+
"""Main post-processing dispatcher loop."""
|
|
41
|
+
self.logger.info(f"Started post-processing worker {self.worker_id}")
|
|
42
|
+
|
|
43
|
+
while self.running:
|
|
44
|
+
try:
|
|
45
|
+
# Get task from post-processing queue
|
|
46
|
+
try:
|
|
47
|
+
priority, timestamp, task_data = self.postproc_queue.get(timeout=self.message_timeout)
|
|
48
|
+
except queue.Empty:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
# Process post-processing task
|
|
52
|
+
self._process_postproc_task(priority, task_data)
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
self.logger.error(f"Post-processing worker error: {e}")
|
|
56
|
+
|
|
57
|
+
self.logger.info(f"Post-processing worker {self.worker_id} stopped")
|
|
58
|
+
|
|
59
|
+
def _process_postproc_task(self, priority: int, task_data: Dict[str, Any]):
|
|
60
|
+
"""Process a single post-processing task."""
|
|
61
|
+
try:
|
|
62
|
+
# Submit to thread pool for async execution
|
|
63
|
+
future = self.postprocessing_executor.submit(self._run_post_processing, task_data)
|
|
64
|
+
result = future.result(timeout=self.inference_timeout)
|
|
65
|
+
|
|
66
|
+
if result["success"]:
|
|
67
|
+
# Cache disabled: preserving content in output and not pushing to Redis
|
|
68
|
+
# try:
|
|
69
|
+
# orig_input = task_data.get("input_stream", {}) or {}
|
|
70
|
+
# content = orig_input.get("content")
|
|
71
|
+
# frame_id_for_cache = task_data.get("frame_id") or orig_input.get("frame_id")
|
|
72
|
+
# if content and frame_id_for_cache and self.frame_cache:
|
|
73
|
+
# if isinstance(content, bytes):
|
|
74
|
+
# import base64
|
|
75
|
+
# try:
|
|
76
|
+
# content = base64.b64encode(content).decode("ascii")
|
|
77
|
+
# except Exception:
|
|
78
|
+
# content = None
|
|
79
|
+
# if isinstance(content, str):
|
|
80
|
+
# self.frame_cache.put(frame_id_for_cache, content)
|
|
81
|
+
# except Exception:
|
|
82
|
+
# pass
|
|
83
|
+
|
|
84
|
+
# Create final output message
|
|
85
|
+
# Prepare input_stream for output: ensure frame_id is present and strip bulky content
|
|
86
|
+
safe_input_stream = {}
|
|
87
|
+
try:
|
|
88
|
+
if isinstance(task_data.get("input_stream"), dict):
|
|
89
|
+
safe_input_stream = dict(task_data["input_stream"]) # shallow copy
|
|
90
|
+
# Ensure frame_id propagation
|
|
91
|
+
if "frame_id" not in safe_input_stream and "frame_id" in task_data:
|
|
92
|
+
safe_input_stream["frame_id"] = task_data["frame_id"]
|
|
93
|
+
# Do not strip content; keep as-is in output
|
|
94
|
+
# if "content" in safe_input_stream:
|
|
95
|
+
# safe_input_stream["content"] = ""
|
|
96
|
+
except Exception:
|
|
97
|
+
safe_input_stream = task_data.get("input_stream", {})
|
|
98
|
+
|
|
99
|
+
# Determine frame_id for top-level convenience
|
|
100
|
+
frame_id = task_data.get("frame_id")
|
|
101
|
+
if not frame_id and isinstance(safe_input_stream, dict):
|
|
102
|
+
frame_id = safe_input_stream.get("frame_id")
|
|
103
|
+
|
|
104
|
+
output_data = {
|
|
105
|
+
"camera_id": task_data["original_message"].camera_id,
|
|
106
|
+
"message_key": task_data["original_message"].message_key,
|
|
107
|
+
"timestamp": task_data["original_message"].timestamp.isoformat(),
|
|
108
|
+
"frame_id": frame_id,
|
|
109
|
+
"model_result": task_data["model_result"],
|
|
110
|
+
"input_stream": safe_input_stream,
|
|
111
|
+
"post_processing_result": result["post_processing_result"],
|
|
112
|
+
"processing_time_sec": task_data["processing_time"],
|
|
113
|
+
"metadata": task_data.get("metadata", {})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Add to output queue
|
|
117
|
+
output_task = {
|
|
118
|
+
"camera_id": task_data["original_message"].camera_id,
|
|
119
|
+
"message_key": task_data["original_message"].message_key,
|
|
120
|
+
"data": output_data,
|
|
121
|
+
}
|
|
122
|
+
# Add to output queue with timestamp as tie-breaker
|
|
123
|
+
self.output_queue.put((priority, time.time(), output_task))
|
|
124
|
+
else:
|
|
125
|
+
self.logger.error(f"Post-processing failed: {result['error']}")
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
self.logger.error(f"Post-processing task error: {e}")
|
|
129
|
+
|
|
130
|
+
def _run_post_processing(self, task_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
131
|
+
"""Run post-processing in thread pool."""
|
|
132
|
+
try:
|
|
133
|
+
if self.post_processor is None:
|
|
134
|
+
raise ValueError("Post processor not initialized")
|
|
135
|
+
|
|
136
|
+
# Extract task data
|
|
137
|
+
model_result = task_data["model_result"]
|
|
138
|
+
input_stream_data = task_data.get("input_stream", {})
|
|
139
|
+
input_content = input_stream_data.get("content")
|
|
140
|
+
|
|
141
|
+
# Handle base64 encoded content
|
|
142
|
+
if input_content and isinstance(input_content, str):
|
|
143
|
+
import base64
|
|
144
|
+
try:
|
|
145
|
+
input_content = base64.b64decode(input_content)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logging.warning(f"Failed to decode base64 input: {str(e)}")
|
|
148
|
+
input_content = None
|
|
149
|
+
|
|
150
|
+
stream_key = task_data.get("stream_key")
|
|
151
|
+
stream_info = input_stream_data.get("stream_info", {})
|
|
152
|
+
camera_config = task_data.get("camera_config", {})
|
|
153
|
+
|
|
154
|
+
# Create event loop for this thread if it doesn't exist
|
|
155
|
+
try:
|
|
156
|
+
loop = asyncio.get_event_loop()
|
|
157
|
+
except RuntimeError:
|
|
158
|
+
loop = asyncio.new_event_loop()
|
|
159
|
+
asyncio.set_event_loop(loop)
|
|
160
|
+
|
|
161
|
+
# Perform post-processing
|
|
162
|
+
result = loop.run_until_complete(
|
|
163
|
+
self.post_processor.process(
|
|
164
|
+
data=model_result,
|
|
165
|
+
input_bytes=input_content if isinstance(input_content, bytes) else None,
|
|
166
|
+
stream_key=stream_key,
|
|
167
|
+
stream_info=stream_info
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# For face recognition use case, return empty raw results
|
|
172
|
+
processed_raw_results = []
|
|
173
|
+
try:
|
|
174
|
+
if hasattr(result, 'usecase') and not result.usecase == 'face_recognition':
|
|
175
|
+
processed_raw_results = model_result
|
|
176
|
+
except Exception as e:
|
|
177
|
+
logging.warning(f"Failed to get processed raw results: {str(e)}")
|
|
178
|
+
|
|
179
|
+
# Extract agg_summary from result data if available
|
|
180
|
+
agg_summary = {}
|
|
181
|
+
try:
|
|
182
|
+
if hasattr(result, 'data') and isinstance(result.data, dict):
|
|
183
|
+
agg_summary = result.data.get("agg_summary", {})
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logging.warning(f"Failed to get agg summary: {str(e)}")
|
|
186
|
+
|
|
187
|
+
# Format result similar to InferenceInterface
|
|
188
|
+
if result.is_success():
|
|
189
|
+
post_processing_result = {
|
|
190
|
+
"status": "success",
|
|
191
|
+
"processing_time": result.processing_time,
|
|
192
|
+
"usecase": getattr(result, 'usecase', ''),
|
|
193
|
+
"category": getattr(result, 'category', ''),
|
|
194
|
+
"summary": getattr(result, 'summary', ''),
|
|
195
|
+
"insights": getattr(result, 'insights', []),
|
|
196
|
+
"metrics": getattr(result, 'metrics', {}),
|
|
197
|
+
"predictions": getattr(result, 'predictions', []),
|
|
198
|
+
"agg_summary": agg_summary,
|
|
199
|
+
"raw_results": processed_raw_results,
|
|
200
|
+
"stream_key": stream_key
|
|
201
|
+
}
|
|
202
|
+
else:
|
|
203
|
+
post_processing_result = {
|
|
204
|
+
"status": "post_processing_failed",
|
|
205
|
+
"error": result.error_message,
|
|
206
|
+
"error_type": getattr(result, 'error_type', 'ProcessingError'),
|
|
207
|
+
"processing_time": result.processing_time,
|
|
208
|
+
"stream_key": stream_key,
|
|
209
|
+
"agg_summary": agg_summary,
|
|
210
|
+
"raw_results": model_result
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
"post_processing_result": post_processing_result,
|
|
215
|
+
"success": True,
|
|
216
|
+
"error": None
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logging.error(f"Post-processing worker error: {str(e)}", exc_info=True)
|
|
221
|
+
return {
|
|
222
|
+
"post_processing_result": {
|
|
223
|
+
"status": "post_processing_failed",
|
|
224
|
+
"error": str(e),
|
|
225
|
+
"error_type": type(e).__name__,
|
|
226
|
+
"stream_key": task_data.get("stream_key")
|
|
227
|
+
},
|
|
228
|
+
"success": False,
|
|
229
|
+
"error": str(e)
|
|
230
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
import queue
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
from matrice_common.stream.matrice_stream import MatriceStream
|
|
9
|
+
from matrice_inference.server.stream.utils import CameraConfig
|
|
10
|
+
|
|
11
|
+
class ProducerWorker:
|
|
12
|
+
"""Handles message production to streams."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, worker_id: int, output_queue: queue.PriorityQueue,
|
|
15
|
+
camera_configs: Dict[str, CameraConfig], message_timeout: float):
|
|
16
|
+
self.worker_id = worker_id
|
|
17
|
+
self.output_queue = output_queue
|
|
18
|
+
self.camera_configs = camera_configs
|
|
19
|
+
self.message_timeout = message_timeout
|
|
20
|
+
self.running = False
|
|
21
|
+
self.producer_streams = {} # Will be created in worker thread's event loop
|
|
22
|
+
self.logger = logging.getLogger(f"{__name__}.producer.{worker_id}")
|
|
23
|
+
|
|
24
|
+
def start(self):
|
|
25
|
+
"""Start the producer worker in a separate thread."""
|
|
26
|
+
self.running = True
|
|
27
|
+
thread = threading.Thread(target=self._run, name=f"ProducerWorker-{self.worker_id}", daemon=False)
|
|
28
|
+
thread.start()
|
|
29
|
+
return thread
|
|
30
|
+
|
|
31
|
+
def stop(self):
|
|
32
|
+
"""Stop the producer worker."""
|
|
33
|
+
self.running = False
|
|
34
|
+
|
|
35
|
+
def _run(self):
|
|
36
|
+
"""Main producer loop."""
|
|
37
|
+
loop = asyncio.new_event_loop()
|
|
38
|
+
asyncio.set_event_loop(loop)
|
|
39
|
+
|
|
40
|
+
self.logger.info(f"Started producer worker {self.worker_id}")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# Initialize streams for all cameras in this event loop
|
|
44
|
+
loop.run_until_complete(self._initialize_streams())
|
|
45
|
+
|
|
46
|
+
while self.running:
|
|
47
|
+
try:
|
|
48
|
+
# Get task from output queue
|
|
49
|
+
try:
|
|
50
|
+
priority, timestamp, task_data = self.output_queue.get(timeout=self.message_timeout)
|
|
51
|
+
except queue.Empty:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
# Send message to stream
|
|
55
|
+
loop.run_until_complete(self._send_message_safely(task_data))
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
self.logger.error(f"Producer error: {e}")
|
|
59
|
+
time.sleep(0.1)
|
|
60
|
+
|
|
61
|
+
finally:
|
|
62
|
+
# Clean up streams
|
|
63
|
+
for stream in self.producer_streams.values():
|
|
64
|
+
try:
|
|
65
|
+
loop.run_until_complete(stream.async_close())
|
|
66
|
+
except Exception as e:
|
|
67
|
+
self.logger.error(f"Error closing producer stream: {e}")
|
|
68
|
+
loop.close()
|
|
69
|
+
self.logger.info(f"Producer worker {self.worker_id} stopped")
|
|
70
|
+
|
|
71
|
+
async def _initialize_streams(self):
|
|
72
|
+
"""Initialize producer streams for all cameras in the current event loop."""
|
|
73
|
+
try:
|
|
74
|
+
from matrice_common.stream.matrice_stream import MatriceStream, StreamType
|
|
75
|
+
|
|
76
|
+
for camera_id, camera_config in self.camera_configs.items():
|
|
77
|
+
try:
|
|
78
|
+
stream_config = camera_config.stream_config
|
|
79
|
+
output_topic = camera_config.output_topic
|
|
80
|
+
|
|
81
|
+
# Determine stream type
|
|
82
|
+
stream_type = StreamType.KAFKA if stream_config.get("stream_type", "kafka").lower() == "kafka" else StreamType.REDIS
|
|
83
|
+
|
|
84
|
+
# Create stream configuration
|
|
85
|
+
if stream_type == StreamType.KAFKA:
|
|
86
|
+
stream_params = {
|
|
87
|
+
"bootstrap_servers": stream_config.get("bootstrap_servers", "localhost:9092"),
|
|
88
|
+
"sasl_username": stream_config.get("sasl_username", "matrice-sdk-user"),
|
|
89
|
+
"sasl_password": stream_config.get("sasl_password", "matrice-sdk-password"),
|
|
90
|
+
"sasl_mechanism": stream_config.get("sasl_mechanism", "SCRAM-SHA-256"),
|
|
91
|
+
"security_protocol": stream_config.get("security_protocol", "SASL_PLAINTEXT"),
|
|
92
|
+
}
|
|
93
|
+
else: # Redis
|
|
94
|
+
stream_params = {
|
|
95
|
+
"host": stream_config.get("host", "localhost"),
|
|
96
|
+
"port": stream_config.get("port", 6379),
|
|
97
|
+
"password": stream_config.get("password"),
|
|
98
|
+
"username": stream_config.get("username"),
|
|
99
|
+
"db": stream_config.get("db", 0),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Create and setup producer stream
|
|
103
|
+
producer_stream = MatriceStream(stream_type, **stream_params)
|
|
104
|
+
await producer_stream.async_setup(output_topic)
|
|
105
|
+
self.producer_streams[camera_id] = producer_stream
|
|
106
|
+
|
|
107
|
+
self.logger.info(f"Initialized {stream_type.value} producer stream for camera {camera_id} in worker {self.worker_id}")
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
self.logger.error(f"Failed to initialize producer stream for camera {camera_id}: {e}")
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
self.logger.error(f"Failed to initialize producer streams: {e}")
|
|
115
|
+
raise
|
|
116
|
+
|
|
117
|
+
async def _send_message_safely(self, task_data: Dict[str, Any]):
|
|
118
|
+
"""Send message to the appropriate stream safely."""
|
|
119
|
+
try:
|
|
120
|
+
camera_id = task_data["camera_id"]
|
|
121
|
+
message_key = task_data["message_key"]
|
|
122
|
+
data = task_data["data"]
|
|
123
|
+
|
|
124
|
+
# Check if camera and stream still exist
|
|
125
|
+
if camera_id not in self.producer_streams or camera_id not in self.camera_configs:
|
|
126
|
+
self.logger.warning(f"Camera {camera_id} not found in producer streams or configs")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
camera_config = self.camera_configs[camera_id]
|
|
130
|
+
if not camera_config.enabled:
|
|
131
|
+
self.logger.debug(f"Camera {camera_id} is disabled, skipping message")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# Get producer stream for camera
|
|
135
|
+
producer_stream = self.producer_streams[camera_id]
|
|
136
|
+
output_topic = camera_config.output_topic
|
|
137
|
+
|
|
138
|
+
# Send message to stream
|
|
139
|
+
await producer_stream.async_add_message(
|
|
140
|
+
output_topic,
|
|
141
|
+
json.dumps(data),
|
|
142
|
+
key=message_key
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
self.logger.error(f"Error sending message: {e}")
|
|
147
|
+
|