matrice-streaming 0.1.14__py3-none-any.whl → 0.1.65__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.
- matrice_streaming/__init__.py +44 -32
- matrice_streaming/streaming_gateway/camera_streamer/__init__.py +68 -1
- matrice_streaming/streaming_gateway/camera_streamer/async_camera_worker.py +1388 -0
- matrice_streaming/streaming_gateway/camera_streamer/async_ffmpeg_worker.py +966 -0
- matrice_streaming/streaming_gateway/camera_streamer/camera_streamer.py +188 -24
- matrice_streaming/streaming_gateway/camera_streamer/device_detection.py +507 -0
- matrice_streaming/streaming_gateway/camera_streamer/encoding_pool_manager.py +136 -0
- matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_camera_streamer.py +1048 -0
- matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_config.py +192 -0
- matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_worker_manager.py +470 -0
- matrice_streaming/streaming_gateway/camera_streamer/gstreamer_camera_streamer.py +1368 -0
- matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker.py +1063 -0
- matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker_manager.py +546 -0
- matrice_streaming/streaming_gateway/camera_streamer/message_builder.py +60 -15
- matrice_streaming/streaming_gateway/camera_streamer/nvdec.py +1330 -0
- matrice_streaming/streaming_gateway/camera_streamer/nvdec_worker_manager.py +412 -0
- matrice_streaming/streaming_gateway/camera_streamer/platform_pipelines.py +680 -0
- matrice_streaming/streaming_gateway/camera_streamer/stream_statistics.py +111 -4
- matrice_streaming/streaming_gateway/camera_streamer/video_capture_manager.py +223 -27
- matrice_streaming/streaming_gateway/camera_streamer/worker_manager.py +694 -0
- matrice_streaming/streaming_gateway/debug/__init__.py +27 -2
- matrice_streaming/streaming_gateway/debug/benchmark.py +727 -0
- matrice_streaming/streaming_gateway/debug/debug_gstreamer_gateway.py +599 -0
- matrice_streaming/streaming_gateway/debug/debug_streaming_gateway.py +245 -95
- matrice_streaming/streaming_gateway/debug/debug_utils.py +29 -0
- matrice_streaming/streaming_gateway/debug/test_videoplayback.py +318 -0
- matrice_streaming/streaming_gateway/dynamic_camera_manager.py +656 -39
- matrice_streaming/streaming_gateway/metrics_reporter.py +676 -139
- matrice_streaming/streaming_gateway/streaming_action.py +71 -20
- matrice_streaming/streaming_gateway/streaming_gateway.py +1026 -78
- matrice_streaming/streaming_gateway/streaming_gateway_utils.py +175 -20
- matrice_streaming/streaming_gateway/streaming_status_listener.py +89 -0
- {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/METADATA +1 -1
- matrice_streaming-0.1.65.dist-info/RECORD +56 -0
- matrice_streaming-0.1.14.dist-info/RECORD +0 -38
- {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/WHEEL +0 -0
- {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/top_level.txt +0 -0
|
@@ -22,8 +22,9 @@ class MetricsConfig:
|
|
|
22
22
|
|
|
23
23
|
# Collection and reporting intervals
|
|
24
24
|
collection_interval: float = 1.0 # Collect metrics every second
|
|
25
|
-
reporting_interval: float =
|
|
26
|
-
history_window: int =
|
|
25
|
+
reporting_interval: float = 30.0 # Report aggregated metrics every 30 seconds
|
|
26
|
+
history_window: int = 30 # Keep 30 seconds of history for statistics
|
|
27
|
+
log_interval: float = 300.0 # Log metrics sends every 5 minutes
|
|
27
28
|
|
|
28
29
|
# Kafka configuration
|
|
29
30
|
metrics_topic: str = "streaming_gateway_metrics"
|
|
@@ -113,6 +114,9 @@ class MetricsCollector:
|
|
|
113
114
|
# Track frame counts for FPS calculation
|
|
114
115
|
self.camera_frame_counts: Dict[str, List[tuple]] = {} # camera_id -> [(timestamp, count)]
|
|
115
116
|
|
|
117
|
+
# Track which flow is being used
|
|
118
|
+
self.use_async_workers = getattr(streaming_gateway, 'use_async_workers', False)
|
|
119
|
+
|
|
116
120
|
def collect_snapshot(self) -> Dict[str, Any]:
|
|
117
121
|
"""
|
|
118
122
|
Collect current metrics snapshot from streaming gateway.
|
|
@@ -125,50 +129,111 @@ class MetricsCollector:
|
|
|
125
129
|
# Get overall statistics from streaming gateway
|
|
126
130
|
gateway_stats = self.streaming_gateway.get_statistics()
|
|
127
131
|
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
# Collect per-camera metrics
|
|
134
|
-
camera_metrics = {}
|
|
135
|
-
|
|
136
|
-
# Get active stream keys
|
|
137
|
-
stream_keys = gateway_stats.get("my_stream_keys", [])
|
|
138
|
-
|
|
139
|
-
for stream_key in stream_keys:
|
|
140
|
-
# Get timing statistics for this stream
|
|
141
|
-
timing = camera_streamer.statistics.get_timing_stats(stream_key)
|
|
142
|
-
|
|
143
|
-
if timing:
|
|
144
|
-
# Extract camera_id from stream_key
|
|
145
|
-
# Stream keys are in format like "camera_id_suffix"
|
|
146
|
-
camera_id = stream_key.split('_')[0] if '_' in stream_key else stream_key
|
|
147
|
-
|
|
148
|
-
camera_metrics[camera_id] = {
|
|
149
|
-
"stream_key": stream_key,
|
|
150
|
-
"read_time": timing.get("last_read_time_sec", 0.0), # Camera reading latency
|
|
151
|
-
"write_time": timing.get("last_write_time_sec", 0.0), # Gateway sending latency
|
|
152
|
-
"process_time": timing.get("last_process_time_sec", 0.0), # Total processing time
|
|
153
|
-
"frame_size": timing.get("last_frame_size_bytes", 0), # ACG frame size in bytes
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
# Get transmission stats for frame counts
|
|
157
|
-
transmission_stats = gateway_stats.get("transmission_stats", {})
|
|
158
|
-
|
|
159
|
-
snapshot = {
|
|
160
|
-
"timestamp": time.time(),
|
|
161
|
-
"cameras": camera_metrics,
|
|
162
|
-
"frames_sent": transmission_stats.get("frames_sent_full", 0),
|
|
163
|
-
"total_frames_processed": transmission_stats.get("total_frames_processed", 0),
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return snapshot
|
|
132
|
+
# Route to appropriate collection method based on flow
|
|
133
|
+
if self.use_async_workers:
|
|
134
|
+
return self._collect_async_worker_snapshot(gateway_stats)
|
|
135
|
+
else:
|
|
136
|
+
return self._collect_camera_streamer_snapshot(gateway_stats)
|
|
167
137
|
|
|
168
138
|
except Exception as e:
|
|
169
139
|
logging.error(f"Error collecting metrics snapshot: {e}", exc_info=True)
|
|
170
140
|
return None
|
|
171
141
|
|
|
142
|
+
def _collect_camera_streamer_snapshot(self, gateway_stats: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
143
|
+
"""Collect metrics from original CameraStreamer flow."""
|
|
144
|
+
# Get camera streamer for detailed metrics
|
|
145
|
+
camera_streamer = self.streaming_gateway.camera_streamer
|
|
146
|
+
if not camera_streamer:
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
# Collect per-camera metrics
|
|
150
|
+
camera_metrics = {}
|
|
151
|
+
|
|
152
|
+
# Get active stream keys
|
|
153
|
+
stream_keys = gateway_stats.get("my_stream_keys", [])
|
|
154
|
+
|
|
155
|
+
for stream_key in stream_keys:
|
|
156
|
+
# Get timing statistics for this stream
|
|
157
|
+
timing = camera_streamer.statistics.get_timing_stats(stream_key)
|
|
158
|
+
|
|
159
|
+
if timing:
|
|
160
|
+
# Get camera_id from the streaming gateway mapping
|
|
161
|
+
camera_id = self.streaming_gateway.get_camera_id_for_stream_key(stream_key)
|
|
162
|
+
if not camera_id:
|
|
163
|
+
# Fallback: try to extract from stream_key if mapping not available
|
|
164
|
+
camera_id = stream_key.split('_')[0] if '_' in stream_key else stream_key
|
|
165
|
+
|
|
166
|
+
camera_metrics[camera_id] = {
|
|
167
|
+
"stream_key": stream_key,
|
|
168
|
+
"read_time": timing.get("last_read_time_sec", 0.0), # Camera reading latency
|
|
169
|
+
"write_time": timing.get("last_write_time_sec", 0.0), # Gateway sending latency
|
|
170
|
+
"process_time": timing.get("last_process_time_sec", 0.0), # Total processing time
|
|
171
|
+
"frame_size": timing.get("last_frame_size_bytes", 0), # ACG frame size in bytes
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# Get transmission stats for frame counts
|
|
175
|
+
transmission_stats = gateway_stats.get("transmission_stats", {})
|
|
176
|
+
|
|
177
|
+
snapshot = {
|
|
178
|
+
"timestamp": time.time(),
|
|
179
|
+
"cameras": camera_metrics,
|
|
180
|
+
"frames_sent": transmission_stats.get("frames_sent_full", 0),
|
|
181
|
+
"total_frames_processed": transmission_stats.get("total_frames_processed", 0),
|
|
182
|
+
"flow_type": "camera_streamer",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return snapshot
|
|
186
|
+
|
|
187
|
+
def _collect_async_worker_snapshot(self, gateway_stats: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
188
|
+
"""Collect metrics from new async worker flow."""
|
|
189
|
+
worker_manager = self.streaming_gateway.worker_manager
|
|
190
|
+
if not worker_manager:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
# Collect per-camera metrics from worker statistics
|
|
194
|
+
camera_metrics = {}
|
|
195
|
+
|
|
196
|
+
# Get active stream keys
|
|
197
|
+
stream_keys = gateway_stats.get("my_stream_keys", [])
|
|
198
|
+
|
|
199
|
+
# Get worker statistics
|
|
200
|
+
worker_stats = gateway_stats.get("worker_stats", {})
|
|
201
|
+
health_reports = worker_stats.get("health_reports", {})
|
|
202
|
+
|
|
203
|
+
for stream_key in stream_keys:
|
|
204
|
+
# Get camera_id from the streaming gateway mapping
|
|
205
|
+
camera_id = self.streaming_gateway.get_camera_id_for_stream_key(stream_key)
|
|
206
|
+
if not camera_id:
|
|
207
|
+
camera_id = stream_key.split('_')[0] if '_' in stream_key else stream_key
|
|
208
|
+
|
|
209
|
+
# For async workers, we track basic info (detailed timing not yet available)
|
|
210
|
+
camera_metrics[camera_id] = {
|
|
211
|
+
"stream_key": stream_key,
|
|
212
|
+
"read_time": 0.0, # Not tracked per-camera in async flow yet
|
|
213
|
+
"write_time": 0.0, # Not tracked per-camera in async flow yet
|
|
214
|
+
"process_time": 0.0, # Not tracked per-camera in async flow yet
|
|
215
|
+
"frame_size": 0, # Not tracked per-camera in async flow yet
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# Calculate aggregate stats from worker health reports
|
|
219
|
+
total_cameras = worker_stats.get("total_cameras", len(stream_keys))
|
|
220
|
+
running_workers = worker_stats.get("running_workers", 0)
|
|
221
|
+
|
|
222
|
+
snapshot = {
|
|
223
|
+
"timestamp": time.time(),
|
|
224
|
+
"cameras": camera_metrics,
|
|
225
|
+
"frames_sent": 0, # Not tracked in async flow yet
|
|
226
|
+
"total_frames_processed": 0, # Not tracked in async flow yet
|
|
227
|
+
"flow_type": "async_workers",
|
|
228
|
+
"worker_stats": {
|
|
229
|
+
"num_workers": worker_stats.get("num_workers", 0),
|
|
230
|
+
"running_workers": running_workers,
|
|
231
|
+
"total_cameras": total_cameras,
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return snapshot
|
|
236
|
+
|
|
172
237
|
def add_to_history(self, snapshot: Dict[str, Any]):
|
|
173
238
|
"""
|
|
174
239
|
Add snapshot to rolling history window.
|
|
@@ -201,102 +266,212 @@ class MetricsCollector:
|
|
|
201
266
|
return None
|
|
202
267
|
|
|
203
268
|
try:
|
|
204
|
-
#
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
269
|
+
# Route to appropriate aggregation method based on flow
|
|
270
|
+
if self.use_async_workers:
|
|
271
|
+
return self._get_async_worker_aggregated_metrics()
|
|
272
|
+
else:
|
|
273
|
+
return self._get_camera_streamer_aggregated_metrics()
|
|
208
274
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
for camera_id, metrics in snapshot.get("cameras", {}).items():
|
|
213
|
-
stream_keys.add(metrics.get("stream_key"))
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logging.error(f"Error calculating aggregated metrics: {e}", exc_info=True)
|
|
277
|
+
return None
|
|
214
278
|
|
|
215
|
-
|
|
216
|
-
|
|
279
|
+
def _get_camera_streamer_aggregated_metrics(self) -> Optional[List[Dict[str, Any]]]:
|
|
280
|
+
"""Get aggregated metrics for CameraStreamer flow."""
|
|
281
|
+
# Get camera streamer for accessing timing statistics
|
|
282
|
+
camera_streamer = self.streaming_gateway.camera_streamer
|
|
283
|
+
if not camera_streamer:
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
# Get active stream keys from the most recent snapshot
|
|
287
|
+
stream_keys = set()
|
|
288
|
+
for snapshot in self.metrics_history:
|
|
289
|
+
for camera_id, metrics in snapshot.get("cameras", {}).items():
|
|
290
|
+
stream_keys.add(metrics.get("stream_key"))
|
|
291
|
+
|
|
292
|
+
# Calculate statistics for each stream using accumulated history
|
|
293
|
+
per_camera_metrics = []
|
|
294
|
+
|
|
295
|
+
for stream_key in stream_keys:
|
|
296
|
+
if not stream_key:
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
# Get real statistics from accumulated timing history
|
|
300
|
+
stats = camera_streamer.statistics.get_timing_statistics(stream_key)
|
|
301
|
+
|
|
302
|
+
if not stats:
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
# Get camera_id from the streaming gateway mapping
|
|
306
|
+
camera_id = self.streaming_gateway.get_camera_id_for_stream_key(stream_key)
|
|
307
|
+
if not camera_id:
|
|
308
|
+
# Fallback: try to extract from stream_key if mapping not available
|
|
309
|
+
camera_id = stream_key.split('_')[0] if '_' in stream_key else stream_key
|
|
310
|
+
|
|
311
|
+
# Get read time statistics (already in milliseconds)
|
|
312
|
+
read_time_ms = stats.get("read_time_ms", {})
|
|
313
|
+
read_stats = {
|
|
314
|
+
"min": read_time_ms.get("min", 0.0),
|
|
315
|
+
"max": read_time_ms.get("max", 0.0),
|
|
316
|
+
"avg": read_time_ms.get("avg", 0.0),
|
|
317
|
+
"p0": read_time_ms.get("min", 0.0),
|
|
318
|
+
"p50": read_time_ms.get("avg", 0.0), # Use avg as approximation for median
|
|
319
|
+
"p100": read_time_ms.get("max", 0.0),
|
|
320
|
+
"unit": "ms"
|
|
321
|
+
}
|
|
217
322
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
323
|
+
# Get write time statistics (already in milliseconds)
|
|
324
|
+
write_time_ms = stats.get("write_time_ms", {})
|
|
325
|
+
write_stats = {
|
|
326
|
+
"min": write_time_ms.get("min", 0.0),
|
|
327
|
+
"max": write_time_ms.get("max", 0.0),
|
|
328
|
+
"avg": write_time_ms.get("avg", 0.0),
|
|
329
|
+
"p0": write_time_ms.get("min", 0.0),
|
|
330
|
+
"p50": write_time_ms.get("avg", 0.0),
|
|
331
|
+
"p100": write_time_ms.get("max", 0.0),
|
|
332
|
+
"unit": "ms"
|
|
333
|
+
}
|
|
221
334
|
|
|
222
|
-
|
|
223
|
-
|
|
335
|
+
# Get FPS statistics (real calculations from timestamps)
|
|
336
|
+
fps_data = stats.get("fps", {})
|
|
337
|
+
fps_stats = {
|
|
338
|
+
"min": fps_data.get("min", 0.0),
|
|
339
|
+
"max": fps_data.get("max", 0.0),
|
|
340
|
+
"avg": fps_data.get("avg", 0.0),
|
|
341
|
+
"p0": fps_data.get("min", 0.0),
|
|
342
|
+
"p50": fps_data.get("avg", 0.0),
|
|
343
|
+
"p100": fps_data.get("max", 0.0),
|
|
344
|
+
"unit": "fps"
|
|
345
|
+
}
|
|
224
346
|
|
|
225
|
-
|
|
226
|
-
|
|
347
|
+
# Get frame size statistics
|
|
348
|
+
frame_size_data = stats.get("frame_size_bytes", {})
|
|
349
|
+
frame_size_stats = {
|
|
350
|
+
"min": frame_size_data.get("min", 0.0),
|
|
351
|
+
"max": frame_size_data.get("max", 0.0),
|
|
352
|
+
"avg": frame_size_data.get("avg", 0.0),
|
|
353
|
+
"p0": frame_size_data.get("min", 0.0),
|
|
354
|
+
"p50": frame_size_data.get("avg", 0.0),
|
|
355
|
+
"p100": frame_size_data.get("max", 0.0),
|
|
356
|
+
"unit": "bytes"
|
|
357
|
+
}
|
|
227
358
|
|
|
228
|
-
|
|
229
|
-
|
|
359
|
+
# Build camera metrics in the required format
|
|
360
|
+
camera_metric = {
|
|
361
|
+
"camera_id": camera_id,
|
|
362
|
+
"camera_reading": {
|
|
363
|
+
"throughput": fps_stats,
|
|
364
|
+
"latency": read_stats
|
|
365
|
+
},
|
|
366
|
+
"gateway_sending": {
|
|
367
|
+
"throughput": fps_stats, # Same as camera reading
|
|
368
|
+
"latency": write_stats
|
|
369
|
+
},
|
|
370
|
+
"frame_size_stats": frame_size_stats
|
|
371
|
+
}
|
|
230
372
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
# Build camera metrics in the required format
|
|
280
|
-
camera_metric = {
|
|
281
|
-
"camera_id": camera_id,
|
|
282
|
-
"camera_reading": {
|
|
283
|
-
"throughput": fps_stats,
|
|
284
|
-
"latency": read_stats
|
|
285
|
-
},
|
|
286
|
-
"gateway_sending": {
|
|
287
|
-
"throughput": fps_stats, # Same as camera reading
|
|
288
|
-
"latency": write_stats
|
|
289
|
-
},
|
|
290
|
-
"acg_frame_size": frame_size_stats
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
per_camera_metrics.append(camera_metric)
|
|
294
|
-
|
|
295
|
-
return per_camera_metrics
|
|
373
|
+
per_camera_metrics.append(camera_metric)
|
|
374
|
+
|
|
375
|
+
return per_camera_metrics
|
|
376
|
+
|
|
377
|
+
def _get_async_worker_aggregated_metrics(self) -> Optional[List[Dict[str, Any]]]:
|
|
378
|
+
"""Get aggregated metrics for async worker flow."""
|
|
379
|
+
worker_manager = self.streaming_gateway.worker_manager
|
|
380
|
+
if not worker_manager:
|
|
381
|
+
return None
|
|
382
|
+
|
|
383
|
+
# Get active stream keys from the most recent snapshot
|
|
384
|
+
stream_keys = set()
|
|
385
|
+
for snapshot in self.metrics_history:
|
|
386
|
+
for camera_id, metrics in snapshot.get("cameras", {}).items():
|
|
387
|
+
stream_keys.add(metrics.get("stream_key"))
|
|
388
|
+
|
|
389
|
+
# Get worker statistics (includes per_camera_stats from health reports)
|
|
390
|
+
gateway_stats = self.streaming_gateway.get_statistics()
|
|
391
|
+
worker_stats = gateway_stats.get("worker_stats", {})
|
|
392
|
+
per_camera_stats = worker_stats.get("per_camera_stats", {})
|
|
393
|
+
|
|
394
|
+
# Build per-camera metrics using real stats from workers
|
|
395
|
+
per_camera_metrics = []
|
|
396
|
+
|
|
397
|
+
for stream_key in stream_keys:
|
|
398
|
+
if not stream_key:
|
|
399
|
+
continue
|
|
400
|
+
|
|
401
|
+
# Get camera_id from the streaming gateway mapping
|
|
402
|
+
camera_id = self.streaming_gateway.get_camera_id_for_stream_key(stream_key)
|
|
403
|
+
if not camera_id:
|
|
404
|
+
camera_id = stream_key.split('_')[0] if '_' in stream_key else stream_key
|
|
405
|
+
|
|
406
|
+
# Get real stats from worker health reports if available
|
|
407
|
+
camera_stats = per_camera_stats.get(stream_key, {})
|
|
408
|
+
|
|
409
|
+
# Build FPS stats from worker data
|
|
410
|
+
fps_data = camera_stats.get('fps', {})
|
|
411
|
+
fps_stats = {
|
|
412
|
+
"min": fps_data.get("min", 0.0),
|
|
413
|
+
"max": fps_data.get("max", 0.0),
|
|
414
|
+
"avg": fps_data.get("avg", 0.0),
|
|
415
|
+
"p0": fps_data.get("min", 0.0),
|
|
416
|
+
"p50": fps_data.get("avg", 0.0),
|
|
417
|
+
"p100": fps_data.get("max", 0.0),
|
|
418
|
+
"unit": "fps"
|
|
419
|
+
}
|
|
296
420
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
421
|
+
# Build read latency stats (already in ms from worker)
|
|
422
|
+
read_time_ms = camera_stats.get('read_time_ms', {})
|
|
423
|
+
read_stats = {
|
|
424
|
+
"min": read_time_ms.get("min", 0.0),
|
|
425
|
+
"max": read_time_ms.get("max", 0.0),
|
|
426
|
+
"avg": read_time_ms.get("avg", 0.0),
|
|
427
|
+
"p0": read_time_ms.get("min", 0.0),
|
|
428
|
+
"p50": read_time_ms.get("avg", 0.0),
|
|
429
|
+
"p100": read_time_ms.get("max", 0.0),
|
|
430
|
+
"unit": "ms"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
# Build write latency stats (already in ms from worker)
|
|
434
|
+
write_time_ms = camera_stats.get('write_time_ms', {})
|
|
435
|
+
write_stats = {
|
|
436
|
+
"min": write_time_ms.get("min", 0.0),
|
|
437
|
+
"max": write_time_ms.get("max", 0.0),
|
|
438
|
+
"avg": write_time_ms.get("avg", 0.0),
|
|
439
|
+
"p0": write_time_ms.get("min", 0.0),
|
|
440
|
+
"p50": write_time_ms.get("avg", 0.0),
|
|
441
|
+
"p100": write_time_ms.get("max", 0.0),
|
|
442
|
+
"unit": "ms"
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
# Build frame size stats (in bytes from worker)
|
|
446
|
+
frame_size_data = camera_stats.get('frame_size_bytes', {})
|
|
447
|
+
frame_size_stats = {
|
|
448
|
+
"min": frame_size_data.get("min", 0.0),
|
|
449
|
+
"max": frame_size_data.get("max", 0.0),
|
|
450
|
+
"avg": frame_size_data.get("avg", 0.0),
|
|
451
|
+
"p0": frame_size_data.get("min", 0.0),
|
|
452
|
+
"p50": frame_size_data.get("avg", 0.0),
|
|
453
|
+
"p100": frame_size_data.get("max", 0.0),
|
|
454
|
+
"unit": "bytes"
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
# Build camera metrics with real data
|
|
458
|
+
camera_metric = {
|
|
459
|
+
"camera_id": camera_id,
|
|
460
|
+
"camera_reading": {
|
|
461
|
+
"throughput": fps_stats,
|
|
462
|
+
"latency": read_stats
|
|
463
|
+
},
|
|
464
|
+
"gateway_sending": {
|
|
465
|
+
"throughput": fps_stats, # Same throughput for gateway sending
|
|
466
|
+
"latency": write_stats
|
|
467
|
+
},
|
|
468
|
+
"frame_size_stats": frame_size_stats,
|
|
469
|
+
"flow_type": "async_workers"
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
per_camera_metrics.append(camera_metric)
|
|
473
|
+
|
|
474
|
+
return per_camera_metrics
|
|
300
475
|
|
|
301
476
|
|
|
302
477
|
class MetricsReporter:
|
|
@@ -390,7 +565,7 @@ class MetricsReporter:
|
|
|
390
565
|
# Wait for send to complete with timeout
|
|
391
566
|
future.get(timeout=self.config.kafka_timeout)
|
|
392
567
|
|
|
393
|
-
|
|
568
|
+
# Logging is handled by MetricsManager to avoid excessive logs
|
|
394
569
|
return True
|
|
395
570
|
|
|
396
571
|
except Exception as e:
|
|
@@ -532,6 +707,10 @@ class MetricsManager:
|
|
|
532
707
|
calculates statistics, and reports them via Kafka.
|
|
533
708
|
"""
|
|
534
709
|
|
|
710
|
+
# ANSI escape codes for BOLD text in terminal
|
|
711
|
+
BOLD = "\033[1m"
|
|
712
|
+
RESET = "\033[0m"
|
|
713
|
+
|
|
535
714
|
def __init__(
|
|
536
715
|
self,
|
|
537
716
|
streaming_gateway,
|
|
@@ -560,11 +739,20 @@ class MetricsManager:
|
|
|
560
739
|
self.collector = MetricsCollector(streaming_gateway, self.config)
|
|
561
740
|
self.reporter = MetricsReporter(session, streaming_gateway_id, self.config)
|
|
562
741
|
|
|
742
|
+
# Track which flow is being used
|
|
743
|
+
self.use_async_workers = getattr(streaming_gateway, 'use_async_workers', False)
|
|
744
|
+
|
|
563
745
|
# Tracking
|
|
564
746
|
self.last_report_time = 0
|
|
747
|
+
self.last_log_time = 0
|
|
748
|
+
self.last_metrics_log_time = 0
|
|
749
|
+
self.metrics_log_interval = 60.0 # Log metrics summary every 60 seconds
|
|
750
|
+
self.last_aggregate_log_time = 0
|
|
751
|
+
self.aggregate_log_interval = 60.0 # Log aggregate metrics with BOLD every 60 seconds
|
|
565
752
|
self.enabled = True
|
|
566
753
|
|
|
567
|
-
|
|
754
|
+
flow_type = "async_workers" if self.use_async_workers else "camera_streamer"
|
|
755
|
+
logging.info(f"Metrics manager initialized (flow: {flow_type})")
|
|
568
756
|
|
|
569
757
|
def collect_and_report(self):
|
|
570
758
|
"""
|
|
@@ -582,8 +770,19 @@ class MetricsManager:
|
|
|
582
770
|
if snapshot:
|
|
583
771
|
self.collector.add_to_history(snapshot)
|
|
584
772
|
|
|
585
|
-
# Report if interval has elapsed
|
|
586
773
|
current_time = time.time()
|
|
774
|
+
|
|
775
|
+
# Log metrics summary periodically
|
|
776
|
+
if current_time - self.last_metrics_log_time >= self.metrics_log_interval:
|
|
777
|
+
self._log_metrics_summary(snapshot)
|
|
778
|
+
self.last_metrics_log_time = current_time
|
|
779
|
+
|
|
780
|
+
# Log aggregate metrics with BOLD every minute
|
|
781
|
+
if current_time - self.last_aggregate_log_time >= self.aggregate_log_interval:
|
|
782
|
+
self._log_aggregate_metrics_bold()
|
|
783
|
+
self.last_aggregate_log_time = current_time
|
|
784
|
+
|
|
785
|
+
# Report if interval has elapsed
|
|
587
786
|
if current_time - self.last_report_time >= self.config.reporting_interval:
|
|
588
787
|
self._generate_and_send_report()
|
|
589
788
|
self.last_report_time = current_time
|
|
@@ -591,6 +790,331 @@ class MetricsManager:
|
|
|
591
790
|
except Exception as e:
|
|
592
791
|
logging.error(f"Error in metrics collect_and_report: {e}", exc_info=True)
|
|
593
792
|
|
|
793
|
+
def _log_metrics_summary(self, snapshot: Optional[Dict[str, Any]]):
|
|
794
|
+
"""Log a summary of current metrics to console."""
|
|
795
|
+
try:
|
|
796
|
+
gateway_stats = self.streaming_gateway.get_statistics()
|
|
797
|
+
|
|
798
|
+
if self.use_async_workers:
|
|
799
|
+
self._log_async_worker_metrics(gateway_stats, snapshot)
|
|
800
|
+
else:
|
|
801
|
+
self._log_camera_streamer_metrics(gateway_stats, snapshot)
|
|
802
|
+
|
|
803
|
+
except Exception as e:
|
|
804
|
+
logging.warning(f"Error logging metrics summary: {e}")
|
|
805
|
+
|
|
806
|
+
def _log_aggregate_metrics_bold(self):
|
|
807
|
+
"""Log comprehensive aggregate metrics with BOLD formatting every minute.
|
|
808
|
+
|
|
809
|
+
Reports:
|
|
810
|
+
- Overall AVG FPS across all cameras
|
|
811
|
+
- Latency breakdown (read, encode/convert, write averages)
|
|
812
|
+
- Total throughput (sum of all cameras' FPS)
|
|
813
|
+
- Total data throughput (KB/s)
|
|
814
|
+
"""
|
|
815
|
+
try:
|
|
816
|
+
gateway_stats = self.streaming_gateway.get_statistics()
|
|
817
|
+
|
|
818
|
+
if self.use_async_workers:
|
|
819
|
+
self._log_aggregate_async_workers_bold(gateway_stats)
|
|
820
|
+
else:
|
|
821
|
+
self._log_aggregate_camera_streamer_bold(gateway_stats)
|
|
822
|
+
|
|
823
|
+
except Exception as e:
|
|
824
|
+
logging.warning(f"Error logging aggregate metrics: {e}")
|
|
825
|
+
|
|
826
|
+
def _log_aggregate_async_workers_bold(self, gateway_stats: Dict[str, Any]):
|
|
827
|
+
"""Log aggregate metrics for async workers with BOLD formatting."""
|
|
828
|
+
worker_stats = gateway_stats.get("worker_stats", {})
|
|
829
|
+
stream_keys = gateway_stats.get("my_stream_keys", [])
|
|
830
|
+
runtime = gateway_stats.get("runtime_seconds", 0)
|
|
831
|
+
|
|
832
|
+
num_workers = worker_stats.get("num_workers", 0)
|
|
833
|
+
running_workers = worker_stats.get("running_workers", 0)
|
|
834
|
+
total_cameras = worker_stats.get("total_cameras", len(stream_keys))
|
|
835
|
+
per_camera_stats = worker_stats.get("per_camera_stats", {})
|
|
836
|
+
|
|
837
|
+
# Detect SHM mode from worker_manager
|
|
838
|
+
use_shm = False
|
|
839
|
+
shm_format = "N/A"
|
|
840
|
+
worker_manager = getattr(self.streaming_gateway, 'worker_manager', None)
|
|
841
|
+
if worker_manager:
|
|
842
|
+
use_shm = getattr(worker_manager, 'use_shm', False)
|
|
843
|
+
shm_format = getattr(worker_manager, 'shm_frame_format', 'N/A') if use_shm else "N/A"
|
|
844
|
+
|
|
845
|
+
# Aggregate FPS stats
|
|
846
|
+
total_fps = 0.0
|
|
847
|
+
fps_values = []
|
|
848
|
+
for stream_key, stats in per_camera_stats.items():
|
|
849
|
+
fps_avg = stats.get("fps", {}).get("avg", 0)
|
|
850
|
+
if fps_avg > 0:
|
|
851
|
+
total_fps += fps_avg
|
|
852
|
+
fps_values.append(fps_avg)
|
|
853
|
+
|
|
854
|
+
avg_fps = sum(fps_values) / len(fps_values) if fps_values else 0
|
|
855
|
+
min_fps = min(fps_values) if fps_values else 0
|
|
856
|
+
max_fps = max(fps_values) if fps_values else 0
|
|
857
|
+
|
|
858
|
+
# Aggregate latency stats (in ms)
|
|
859
|
+
read_times = []
|
|
860
|
+
write_times = []
|
|
861
|
+
encoding_times = []
|
|
862
|
+
|
|
863
|
+
for stream_key, stats in per_camera_stats.items():
|
|
864
|
+
read_ms = stats.get("read_time_ms", {}).get("avg", 0)
|
|
865
|
+
write_ms = stats.get("write_time_ms", {}).get("avg", 0)
|
|
866
|
+
encoding_ms = stats.get("encoding_time_ms", {}).get("avg", 0)
|
|
867
|
+
|
|
868
|
+
if read_ms > 0:
|
|
869
|
+
read_times.append(read_ms)
|
|
870
|
+
if write_ms > 0:
|
|
871
|
+
write_times.append(write_ms)
|
|
872
|
+
if encoding_ms > 0:
|
|
873
|
+
encoding_times.append(encoding_ms)
|
|
874
|
+
|
|
875
|
+
avg_read_ms = sum(read_times) / len(read_times) if read_times else 0
|
|
876
|
+
avg_write_ms = sum(write_times) / len(write_times) if write_times else 0
|
|
877
|
+
avg_encoding_ms = sum(encoding_times) / len(encoding_times) if encoding_times else 0
|
|
878
|
+
total_latency_ms = avg_read_ms + avg_encoding_ms + avg_write_ms
|
|
879
|
+
|
|
880
|
+
# Aggregate frame size and throughput
|
|
881
|
+
total_frame_size_kb = 0.0
|
|
882
|
+
frame_sizes = []
|
|
883
|
+
for stream_key, stats in per_camera_stats.items():
|
|
884
|
+
frame_size_bytes = stats.get("frame_size_bytes", {}).get("avg", 0)
|
|
885
|
+
if frame_size_bytes > 0:
|
|
886
|
+
frame_sizes.append(frame_size_bytes)
|
|
887
|
+
total_frame_size_kb += frame_size_bytes / 1024
|
|
888
|
+
|
|
889
|
+
avg_frame_size_kb = (sum(frame_sizes) / len(frame_sizes) / 1024) if frame_sizes else 0
|
|
890
|
+
|
|
891
|
+
# Total throughput: sum of (FPS * frame_size) across all cameras = total KB/s
|
|
892
|
+
total_throughput_kbps = 0.0
|
|
893
|
+
for stream_key, stats in per_camera_stats.items():
|
|
894
|
+
fps_avg = stats.get("fps", {}).get("avg", 0)
|
|
895
|
+
frame_size_bytes = stats.get("frame_size_bytes", {}).get("avg", 0)
|
|
896
|
+
if fps_avg > 0 and frame_size_bytes > 0:
|
|
897
|
+
total_throughput_kbps += (fps_avg * frame_size_bytes) / 1024
|
|
898
|
+
|
|
899
|
+
total_throughput_mbps = total_throughput_kbps / 1024
|
|
900
|
+
|
|
901
|
+
# Log with BOLD formatting
|
|
902
|
+
B = self.BOLD
|
|
903
|
+
R = self.RESET
|
|
904
|
+
|
|
905
|
+
# Mode indicator
|
|
906
|
+
mode_str = f"SHM ({shm_format})" if use_shm else "JPEG"
|
|
907
|
+
encode_label = "Convert" if use_shm else "Encode"
|
|
908
|
+
|
|
909
|
+
logging.info(
|
|
910
|
+
f"\n{B}{'='*80}{R}\n"
|
|
911
|
+
f"{B}[STREAMING GATEWAY AGGREGATE METRICS - 1 MIN SUMMARY]{R}\n"
|
|
912
|
+
f"{B}{'='*80}{R}\n"
|
|
913
|
+
f"{B}Mode:{R} {mode_str} | "
|
|
914
|
+
f"{B}Workers:{R} {running_workers}/{num_workers} active | "
|
|
915
|
+
f"{B}Cameras:{R} {total_cameras} streaming | "
|
|
916
|
+
f"{B}Runtime:{R} {runtime:.0f}s\n"
|
|
917
|
+
f"{B}{'─'*80}{R}\n"
|
|
918
|
+
f"{B}FPS:{R} avg={avg_fps:.1f} | min={min_fps:.1f} | max={max_fps:.1f} | "
|
|
919
|
+
f"{B}TOTAL={total_fps:.1f} fps{R}\n"
|
|
920
|
+
f"{B}{'─'*80}{R}\n"
|
|
921
|
+
f"{B}LATENCY BREAKDOWN:{R}\n"
|
|
922
|
+
f" • Read: {avg_read_ms:.2f} ms (avg)\n"
|
|
923
|
+
f" • {encode_label}: {avg_encoding_ms:.2f} ms (avg)\n"
|
|
924
|
+
f" • Write: {avg_write_ms:.2f} ms (avg)\n"
|
|
925
|
+
f" • {B}TOTAL: {total_latency_ms:.2f} ms{R}\n"
|
|
926
|
+
f"{B}{'─'*80}{R}\n"
|
|
927
|
+
f"{B}THROUGHPUT:{R}\n"
|
|
928
|
+
f" • Avg Frame Size: {avg_frame_size_kb:.1f} KB\n"
|
|
929
|
+
f" • {B}TOTAL: {total_throughput_mbps:.2f} MB/s ({total_throughput_kbps:.1f} KB/s){R}\n"
|
|
930
|
+
f"{B}{'='*80}{R}"
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
def _log_aggregate_camera_streamer_bold(self, gateway_stats: Dict[str, Any]):
|
|
934
|
+
"""Log aggregate metrics for CameraStreamer with BOLD formatting."""
|
|
935
|
+
camera_streamer = self.streaming_gateway.camera_streamer
|
|
936
|
+
if not camera_streamer:
|
|
937
|
+
return
|
|
938
|
+
|
|
939
|
+
stream_keys = gateway_stats.get("my_stream_keys", [])
|
|
940
|
+
transmission_stats = gateway_stats.get("transmission_stats", {})
|
|
941
|
+
runtime = gateway_stats.get("runtime_seconds", 0)
|
|
942
|
+
|
|
943
|
+
# Aggregate FPS stats
|
|
944
|
+
fps_values = []
|
|
945
|
+
read_times = []
|
|
946
|
+
write_times = []
|
|
947
|
+
encoding_times = []
|
|
948
|
+
frame_sizes = []
|
|
949
|
+
|
|
950
|
+
# Calculate total throughput properly per-camera
|
|
951
|
+
total_throughput_kbps = 0.0
|
|
952
|
+
|
|
953
|
+
for stream_key in stream_keys:
|
|
954
|
+
timing_stats = camera_streamer.statistics.get_timing_statistics(stream_key)
|
|
955
|
+
if timing_stats:
|
|
956
|
+
fps_avg = timing_stats.get("fps", {}).get("avg", 0)
|
|
957
|
+
if fps_avg > 0:
|
|
958
|
+
fps_values.append(fps_avg)
|
|
959
|
+
|
|
960
|
+
read_ms = timing_stats.get("read_time_ms", {}).get("avg", 0)
|
|
961
|
+
write_ms = timing_stats.get("write_time_ms", {}).get("avg", 0)
|
|
962
|
+
encoding_ms = timing_stats.get("encoding_time_ms", {}).get("avg", 0)
|
|
963
|
+
frame_size_bytes = timing_stats.get("frame_size_bytes", {}).get("avg", 0)
|
|
964
|
+
|
|
965
|
+
if read_ms > 0:
|
|
966
|
+
read_times.append(read_ms)
|
|
967
|
+
if write_ms > 0:
|
|
968
|
+
write_times.append(write_ms)
|
|
969
|
+
if encoding_ms > 0:
|
|
970
|
+
encoding_times.append(encoding_ms)
|
|
971
|
+
if frame_size_bytes > 0:
|
|
972
|
+
frame_sizes.append(frame_size_bytes)
|
|
973
|
+
|
|
974
|
+
# Calculate throughput per camera (FPS * frame_size)
|
|
975
|
+
if fps_avg > 0 and frame_size_bytes > 0:
|
|
976
|
+
total_throughput_kbps += (fps_avg * frame_size_bytes) / 1024
|
|
977
|
+
|
|
978
|
+
total_fps = sum(fps_values)
|
|
979
|
+
avg_fps = sum(fps_values) / len(fps_values) if fps_values else 0
|
|
980
|
+
min_fps = min(fps_values) if fps_values else 0
|
|
981
|
+
max_fps = max(fps_values) if fps_values else 0
|
|
982
|
+
|
|
983
|
+
avg_read_ms = sum(read_times) / len(read_times) if read_times else 0
|
|
984
|
+
avg_write_ms = sum(write_times) / len(write_times) if write_times else 0
|
|
985
|
+
avg_encoding_ms = sum(encoding_times) / len(encoding_times) if encoding_times else 0
|
|
986
|
+
total_latency_ms = avg_read_ms + avg_encoding_ms + avg_write_ms
|
|
987
|
+
|
|
988
|
+
avg_frame_size_kb = (sum(frame_sizes) / len(frame_sizes) / 1024) if frame_sizes else 0
|
|
989
|
+
|
|
990
|
+
total_throughput_mbps = total_throughput_kbps / 1024
|
|
991
|
+
|
|
992
|
+
frames_sent = transmission_stats.get("frames_sent_full", 0)
|
|
993
|
+
frames_skipped = transmission_stats.get("frames_skipped", 0)
|
|
994
|
+
|
|
995
|
+
# Log with BOLD formatting
|
|
996
|
+
B = self.BOLD
|
|
997
|
+
R = self.RESET
|
|
998
|
+
|
|
999
|
+
logging.info(
|
|
1000
|
+
f"\n{B}{'='*80}{R}\n"
|
|
1001
|
+
f"{B}[STREAMING GATEWAY AGGREGATE METRICS - 1 MIN SUMMARY]{R}\n"
|
|
1002
|
+
f"{B}{'='*80}{R}\n"
|
|
1003
|
+
f"{B}Cameras:{R} {len(stream_keys)} streaming | "
|
|
1004
|
+
f"{B}Runtime:{R} {runtime:.0f}s | "
|
|
1005
|
+
f"{B}Frames:{R} sent={frames_sent}, skipped={frames_skipped}\n"
|
|
1006
|
+
f"{B}{'─'*80}{R}\n"
|
|
1007
|
+
f"{B}FPS:{R} avg={avg_fps:.1f} | min={min_fps:.1f} | max={max_fps:.1f} | "
|
|
1008
|
+
f"{B}TOTAL={total_fps:.1f} fps{R}\n"
|
|
1009
|
+
f"{B}{'─'*80}{R}\n"
|
|
1010
|
+
f"{B}LATENCY BREAKDOWN:{R}\n"
|
|
1011
|
+
f" • Read: {avg_read_ms:.2f} ms (avg)\n"
|
|
1012
|
+
f" • Encode: {avg_encoding_ms:.2f} ms (avg)\n"
|
|
1013
|
+
f" • Write: {avg_write_ms:.2f} ms (avg)\n"
|
|
1014
|
+
f" • {B}TOTAL: {total_latency_ms:.2f} ms{R}\n"
|
|
1015
|
+
f"{B}{'─'*80}{R}\n"
|
|
1016
|
+
f"{B}THROUGHPUT:{R}\n"
|
|
1017
|
+
f" • Avg Frame Size: {avg_frame_size_kb:.1f} KB\n"
|
|
1018
|
+
f" • {B}TOTAL: {total_throughput_mbps:.2f} MB/s ({total_throughput_kbps:.1f} KB/s){R}\n"
|
|
1019
|
+
f"{B}{'='*80}{R}"
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
def _log_camera_streamer_metrics(self, gateway_stats: Dict[str, Any], snapshot: Optional[Dict[str, Any]]):
|
|
1023
|
+
"""Log metrics summary for CameraStreamer flow."""
|
|
1024
|
+
camera_streamer = self.streaming_gateway.camera_streamer
|
|
1025
|
+
if not camera_streamer:
|
|
1026
|
+
return
|
|
1027
|
+
|
|
1028
|
+
stream_keys = gateway_stats.get("my_stream_keys", [])
|
|
1029
|
+
transmission_stats = gateway_stats.get("transmission_stats", {})
|
|
1030
|
+
runtime = gateway_stats.get("runtime_seconds", 0)
|
|
1031
|
+
|
|
1032
|
+
# Build per-camera summary with frame size
|
|
1033
|
+
camera_summaries = []
|
|
1034
|
+
total_frame_size_kb = 0
|
|
1035
|
+
camera_count_with_size = 0
|
|
1036
|
+
|
|
1037
|
+
for stream_key in stream_keys[:5]: # Limit to first 5 cameras for log brevity
|
|
1038
|
+
timing_stats = camera_streamer.statistics.get_timing_statistics(stream_key)
|
|
1039
|
+
if timing_stats:
|
|
1040
|
+
read_ms = timing_stats.get("read_time_ms", {}).get("avg", 0.0)
|
|
1041
|
+
write_ms = timing_stats.get("write_time_ms", {}).get("avg", 0.0)
|
|
1042
|
+
frame_size_bytes = timing_stats.get("frame_size_bytes", {}).get("avg", 0)
|
|
1043
|
+
frame_kb = frame_size_bytes / 1024
|
|
1044
|
+
camera_summaries.append(f"{stream_key}(r:{read_ms:.1f}ms,w:{write_ms:.1f}ms,{frame_kb:.1f}KB)")
|
|
1045
|
+
|
|
1046
|
+
if frame_size_bytes > 0:
|
|
1047
|
+
total_frame_size_kb += frame_kb
|
|
1048
|
+
camera_count_with_size += 1
|
|
1049
|
+
|
|
1050
|
+
# Calculate average frame size across all cameras
|
|
1051
|
+
avg_frame_size_kb = total_frame_size_kb / camera_count_with_size if camera_count_with_size > 0 else 0
|
|
1052
|
+
|
|
1053
|
+
frames_sent = transmission_stats.get("frames_sent_full", 0)
|
|
1054
|
+
avg_fps = frames_sent / runtime if runtime > 0 else 0
|
|
1055
|
+
|
|
1056
|
+
logging.info(
|
|
1057
|
+
f"[METRICS] CameraStreamer | "
|
|
1058
|
+
f"cameras={len(stream_keys)} | "
|
|
1059
|
+
f"frames_sent={frames_sent} | "
|
|
1060
|
+
f"avg_fps={avg_fps:.1f} | "
|
|
1061
|
+
f"avg_frame_size={avg_frame_size_kb:.1f}KB | "
|
|
1062
|
+
f"runtime={runtime:.0f}s | "
|
|
1063
|
+
f"samples: {', '.join(camera_summaries[:3])}"
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
def _log_async_worker_metrics(self, gateway_stats: Dict[str, Any], snapshot: Optional[Dict[str, Any]]):
|
|
1067
|
+
"""Log metrics summary for async worker flow."""
|
|
1068
|
+
worker_stats = gateway_stats.get("worker_stats", {})
|
|
1069
|
+
stream_keys = gateway_stats.get("my_stream_keys", [])
|
|
1070
|
+
runtime = gateway_stats.get("runtime_seconds", 0)
|
|
1071
|
+
|
|
1072
|
+
num_workers = worker_stats.get("num_workers", 0)
|
|
1073
|
+
running_workers = worker_stats.get("running_workers", 0)
|
|
1074
|
+
total_cameras = worker_stats.get("total_cameras", len(stream_keys))
|
|
1075
|
+
worker_camera_counts = worker_stats.get("worker_camera_counts", {})
|
|
1076
|
+
health_reports = worker_stats.get("health_reports", {})
|
|
1077
|
+
per_camera_stats = worker_stats.get("per_camera_stats", {})
|
|
1078
|
+
|
|
1079
|
+
# Build worker load summary
|
|
1080
|
+
worker_loads = []
|
|
1081
|
+
for worker_id, count in worker_camera_counts.items():
|
|
1082
|
+
health = health_reports.get(worker_id, {})
|
|
1083
|
+
status = health.get("status", "unknown")
|
|
1084
|
+
worker_loads.append(f"W{worker_id}:{count}({status})")
|
|
1085
|
+
|
|
1086
|
+
# Calculate average frame size across all cameras
|
|
1087
|
+
total_frame_size_kb = 0
|
|
1088
|
+
camera_count_with_size = 0
|
|
1089
|
+
for stream_key, stats in per_camera_stats.items():
|
|
1090
|
+
frame_size_bytes = stats.get("frame_size_bytes", {}).get("avg", 0)
|
|
1091
|
+
if frame_size_bytes > 0:
|
|
1092
|
+
total_frame_size_kb += frame_size_bytes / 1024
|
|
1093
|
+
camera_count_with_size += 1
|
|
1094
|
+
|
|
1095
|
+
avg_frame_size_kb = total_frame_size_kb / camera_count_with_size if camera_count_with_size > 0 else 0
|
|
1096
|
+
|
|
1097
|
+
# Calculate average FPS across all cameras
|
|
1098
|
+
total_fps = 0
|
|
1099
|
+
camera_count_with_fps = 0
|
|
1100
|
+
for stream_key, stats in per_camera_stats.items():
|
|
1101
|
+
fps_avg = stats.get("fps", {}).get("avg", 0)
|
|
1102
|
+
if fps_avg > 0:
|
|
1103
|
+
total_fps += fps_avg
|
|
1104
|
+
camera_count_with_fps += 1
|
|
1105
|
+
|
|
1106
|
+
avg_fps = total_fps / camera_count_with_fps if camera_count_with_fps > 0 else 0
|
|
1107
|
+
|
|
1108
|
+
logging.info(
|
|
1109
|
+
f"[METRICS] AsyncWorkers | "
|
|
1110
|
+
f"workers={running_workers}/{num_workers} | "
|
|
1111
|
+
f"cameras={total_cameras} | "
|
|
1112
|
+
f"avg_fps={avg_fps:.1f} | "
|
|
1113
|
+
f"avg_frame_size={avg_frame_size_kb:.1f}KB | "
|
|
1114
|
+
f"runtime={runtime:.0f}s | "
|
|
1115
|
+
f"distribution: {', '.join(worker_loads[:4])}"
|
|
1116
|
+
)
|
|
1117
|
+
|
|
594
1118
|
def _generate_and_send_report(self):
|
|
595
1119
|
"""Generate metrics report and send to Kafka."""
|
|
596
1120
|
try:
|
|
@@ -602,26 +1126,39 @@ class MetricsManager:
|
|
|
602
1126
|
return
|
|
603
1127
|
|
|
604
1128
|
# Build report in the required format
|
|
1129
|
+
flow_type = "async_workers" if self.use_async_workers else "camera_streamer"
|
|
605
1130
|
report = {
|
|
606
1131
|
"streaming_gateway_id": self.streaming_gateway_id,
|
|
607
1132
|
"action_id": self.action_id or "unknown",
|
|
608
1133
|
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
609
|
-
"per_camera_metrics": per_camera_metrics
|
|
1134
|
+
"per_camera_metrics": per_camera_metrics,
|
|
1135
|
+
"flow_type": flow_type
|
|
610
1136
|
}
|
|
611
1137
|
|
|
612
1138
|
# Send report
|
|
613
1139
|
success = self.reporter.send_metrics(report)
|
|
614
1140
|
|
|
1141
|
+
# Check if we should log (every 5 minutes)
|
|
1142
|
+
current_time = time.time()
|
|
1143
|
+
should_log = (current_time - self.last_log_time >= self.config.log_interval)
|
|
1144
|
+
|
|
615
1145
|
if success:
|
|
616
|
-
|
|
1146
|
+
if should_log:
|
|
1147
|
+
logging.info(f"Metrics report sent successfully ({len(per_camera_metrics)} cameras, flow={flow_type})")
|
|
1148
|
+
self.last_log_time = current_time
|
|
617
1149
|
|
|
618
1150
|
# Clear timing history after successful reporting to prevent unbounded memory growth
|
|
619
|
-
|
|
620
|
-
if
|
|
621
|
-
camera_streamer.
|
|
622
|
-
|
|
1151
|
+
# Only applicable for CameraStreamer flow
|
|
1152
|
+
if not self.use_async_workers:
|
|
1153
|
+
camera_streamer = self.streaming_gateway.camera_streamer
|
|
1154
|
+
if camera_streamer and hasattr(camera_streamer, 'statistics'):
|
|
1155
|
+
camera_streamer.statistics.clear_timing_history()
|
|
1156
|
+
if should_log:
|
|
1157
|
+
logging.debug("Cleared timing history after successful metrics reporting")
|
|
623
1158
|
else:
|
|
624
|
-
|
|
1159
|
+
if should_log:
|
|
1160
|
+
logging.warning(f"Failed to send metrics report (flow={flow_type})")
|
|
1161
|
+
self.last_log_time = current_time
|
|
625
1162
|
|
|
626
1163
|
except Exception as e:
|
|
627
1164
|
logging.error(f"Error generating/sending metrics report: {e}", exc_info=True)
|