25
25
from aiortc .rtcrtpsender import RTCRtpSender
26
26
from aiortc .codecs import h264
27
27
from pipeline import Pipeline
28
- from utils import patch_loop_datagram , StreamStats , add_prefix_to_app_routes
28
+ from utils import patch_loop_datagram , add_prefix_to_app_routes
29
+ from metrics import MetricsManager , StreamStatsManager
29
30
import time
30
31
31
32
logger = logging .getLogger (__name__ )
32
- logging .getLogger (' aiortc.rtcrtpsender' ).setLevel (logging .WARNING )
33
- logging .getLogger (' aiortc.rtcrtpreceiver' ).setLevel (logging .WARNING )
33
+ logging .getLogger (" aiortc.rtcrtpsender" ).setLevel (logging .WARNING )
34
+ logging .getLogger (" aiortc.rtcrtpreceiver" ).setLevel (logging .WARNING )
34
35
35
36
36
37
MAX_BITRATE = 2000000
@@ -45,7 +46,9 @@ class VideoStreamTrack(MediaStreamTrack):
45
46
track (MediaStreamTrack): The underlying media stream track.
46
47
pipeline (Pipeline): The processing pipeline to apply to each video frame.
47
48
"""
49
+
48
50
kind = "video"
51
+
49
52
def __init__ (self , track : MediaStreamTrack , pipeline : Pipeline ):
50
53
"""Initialize the VideoStreamTrack.
51
54
@@ -63,6 +66,7 @@ def __init__(self, track: MediaStreamTrack, pipeline: Pipeline):
63
66
self ._fps_loop_start_time = time .monotonic ()
64
67
self ._fps = 0.0
65
68
self ._fps_measurements = deque (maxlen = 60 )
69
+ self ._average_fps = 0.0
66
70
self ._running_event = asyncio .Event ()
67
71
68
72
asyncio .create_task (self .collect_frames ())
@@ -88,18 +92,36 @@ async def _calculate_fps_loop(self):
88
92
current_time = time .monotonic ()
89
93
if self ._last_fps_calculation_time is not None :
90
94
time_diff = current_time - self ._last_fps_calculation_time
91
- self ._fps = self ._fps_interval_frame_count / time_diff
95
+ self ._fps = (
96
+ self ._fps_interval_frame_count / time_diff
97
+ if time_diff > 0
98
+ else 0.0
99
+ )
92
100
self ._fps_measurements .append (
93
101
{
94
102
"timestamp" : current_time - self ._fps_loop_start_time ,
95
103
"fps" : self ._fps ,
96
104
}
97
105
) # Store the FPS measurement with timestamp
98
106
99
- # Reset start_time and frame_count for the next interval.
107
+ # Store the average FPS over the last minute.
108
+ self ._average_fps = (
109
+ sum (m ["fps" ] for m in self ._fps_measurements )
110
+ / len (self ._fps_measurements )
111
+ if self ._fps_measurements
112
+ else self ._fps
113
+ )
114
+
115
+ # Reset tracking variables for the next interval.
100
116
self ._last_fps_calculation_time = current_time
101
117
self ._fps_interval_frame_count = 0
102
- await asyncio .sleep (1 ) # Calculate FPS every second.
118
+
119
+ # Update Prometheus metrics if enabled.
120
+ app ["metrics_manager" ].update_metrics (
121
+ self .track .id , self ._fps , self ._average_fps
122
+ )
123
+
124
+ await asyncio .sleep (1 ) # Calculate FPS every second
103
125
104
126
@property
105
127
async def fps (self ) -> float :
@@ -129,11 +151,7 @@ async def average_fps(self) -> float:
129
151
The average FPS over the last minute.
130
152
"""
131
153
async with self ._lock :
132
- if not self ._fps_measurements :
133
- return 0.0
134
- return sum (
135
- measurement ["fps" ] for measurement in self ._fps_measurements
136
- ) / len (self ._fps_measurements )
154
+ return self ._average_fps
137
155
138
156
@property
139
157
async def last_fps_calculation_time (self ) -> float :
@@ -159,6 +177,7 @@ async def recv(self):
159
177
160
178
class AudioStreamTrack (MediaStreamTrack ):
161
179
kind = "audio"
180
+
162
181
def __init__ (self , track : MediaStreamTrack , pipeline ):
163
182
super ().__init__ ()
164
183
self .track = track
@@ -257,30 +276,29 @@ async def offer(request):
257
276
@pc .on ("datachannel" )
258
277
def on_datachannel (channel ):
259
278
if channel .label == "control" :
279
+
260
280
@channel .on ("message" )
261
281
async def on_message (message ):
262
282
try :
263
283
params = json .loads (message )
264
284
265
285
if params .get ("type" ) == "get_nodes" :
266
286
nodes_info = await pipeline .get_nodes_info ()
267
- response = {
268
- "type" : "nodes_info" ,
269
- "nodes" : nodes_info
270
- }
287
+ response = {"type" : "nodes_info" , "nodes" : nodes_info }
271
288
channel .send (json .dumps (response ))
272
289
elif params .get ("type" ) == "update_prompts" :
273
290
if "prompts" not in params :
274
- logger .warning ("[Control] Missing prompt in update_prompt message" )
291
+ logger .warning (
292
+ "[Control] Missing prompt in update_prompt message"
293
+ )
275
294
return
276
295
await pipeline .update_prompts (params ["prompts" ])
277
- response = {
278
- "type" : "prompts_updated" ,
279
- "success" : True
280
- }
296
+ response = {"type" : "prompts_updated" , "success" : True }
281
297
channel .send (json .dumps (response ))
282
298
else :
283
- logger .warning ("[Server] Invalid message format - missing required fields" )
299
+ logger .warning (
300
+ "[Server] Invalid message format - missing required fields"
301
+ )
284
302
except json .JSONDecodeError :
285
303
logger .error ("[Server] Invalid JSON received" )
286
304
except Exception as e :
@@ -385,12 +403,18 @@ async def on_shutdown(app: web.Application):
385
403
choices = ["DEBUG" , "INFO" , "WARNING" , "ERROR" , "CRITICAL" ],
386
404
help = "Set the logging level" ,
387
405
)
406
+ parser .add_argument (
407
+ "--monitor" ,
408
+ default = False ,
409
+ action = "store_true" ,
410
+ help = "Start a Prometheus metrics endpoint for monitoring." ,
411
+ )
388
412
args = parser .parse_args ()
389
413
390
414
logging .basicConfig (
391
415
level = args .log_level .upper (),
392
- format = ' %(asctime)s [%(levelname)s] %(message)s' ,
393
- datefmt = ' %H:%M:%S'
416
+ format = " %(asctime)s [%(levelname)s] %(message)s" ,
417
+ datefmt = " %H:%M:%S" ,
394
418
)
395
419
396
420
app = web .Application ()
@@ -408,11 +432,23 @@ async def on_shutdown(app: web.Application):
408
432
app .router .add_post ("/prompt" , set_prompt )
409
433
410
434
# Add routes for getting stream statistics.
411
- stream_stats = StreamStats (app )
412
- app .router .add_get ("/streams/stats" , stream_stats .collect_all_stream_metrics )
435
+ stream_stats_manager = StreamStatsManager (app )
413
436
app .router .add_get (
414
- "/stream/{stream_id}/ stats" , stream_stats . collect_stream_metrics_by_id
437
+ "/streams/ stats" , stream_stats_manager . collect_all_stream_metrics
415
438
)
439
+ app .router .add_get (
440
+ "/stream/{stream_id}/stats" , stream_stats_manager .collect_stream_metrics_by_id
441
+ )
442
+
443
+ # Add Prometheus metrics endpoint.
444
+ app ["metrics_manager" ] = MetricsManager ()
445
+ if args .monitor :
446
+ app ["metrics_manager" ].enable ()
447
+ logger .info (
448
+ f"Monitoring enabled - Prometheus metrics available at: "
449
+ f"http://{ args .host } :{ args .port } /metrics"
450
+ )
451
+ app .router .add_get ("/metrics" , app ["metrics_manager" ].metrics_handler )
416
452
417
453
# Add hosted platform route prefix.
418
454
# NOTE: This ensures that the local and hosted experiences have consistent routes.
0 commit comments