1414import ffmpeg_pb2
1515import ffmpeg_pb2_grpc
1616
17+ from prometheus_client import Gauge , Counter , make_asgi_app
18+ from prometheus_client import start_http_server as start_prometheus_server
19+
1720# Required for MediaInfo and ffmpeg health check
1821import aiofiles
1922from pymediainfo import MediaInfo
3740# Variable to store health status
3841health_status = {'healthy' : False }
3942
43+ # Prometheus metrics
44+ ffmpeg_gauge = Gauge ('ffmpeg_processes' , 'Number of running ffmpeg processes' )
45+ mediainfo_counter = Counter ('mediainfo_commands' , 'Number of mediainfo commands executed' )
46+ ffprobe_counter = Counter ('ffprobe_commands' , 'Number of ffprobe commands executed' )
47+
4048class TokenAuthValidator (grpc .AuthMetadataPlugin ):
4149 def __call__ (self , context , callback ):
4250 token = None
@@ -69,6 +77,17 @@ async def ExecuteCommand(self, request, context):
6977 # Reconstruct the command
7078 command = shlex .join (tokens )
7179
80+ # Track metrics for this command but dont include health check commands
81+ binary = tokens [0 ].split ('/' )[- 1 ]
82+ exclude_health_check = binary == 'ffmpeg' and HEALTHCHECK_FILE in command
83+
84+ if binary == 'ffmpeg' and not exclude_health_check :
85+ ffmpeg_gauge .inc ()
86+ elif binary == 'mediainfo' :
87+ mediainfo_counter .inc ()
88+ elif binary == 'ffprobe' :
89+ ffprobe_counter .inc ()
90+
7291 process = await asyncio .create_subprocess_shell (
7392 command ,
7493 stdout = asyncio .subprocess .PIPE ,
@@ -90,6 +109,11 @@ async def read_stream(stream, response_type, stream_name):
90109 yield response
91110
92111 await process .wait ()
112+
113+ # decrease counter whenever ffmpeg processes complete
114+ if binary == 'ffmpeg' and not exclude_health_check :
115+ ffmpeg_gauge .dec ()
116+
93117 exit_code = process .returncode
94118 yield ffmpeg_pb2 .CommandResponse (exit_code = exit_code , stream = "exit_code" )
95119
@@ -110,6 +134,7 @@ async def run_health_check(self):
110134 logger .error (f"MediaInfo failed for { HEALTHCHECK_FILE } " )
111135 self .update_health_status (False )
112136 return
137+
113138 except asyncio .CancelledError :
114139 logger .info ("Health check task canceled." )
115140 raise
@@ -120,6 +145,7 @@ async def run_health_check(self):
120145 # Run ffmpeg conversion test
121146 ffmpeg_command = f"{ BINARY_PATH_PREFIX } ffmpeg -i { HEALTHCHECK_FILE } { HEALTHCHECK_OUTPUT } "
122147 ffmpeg_output = await self .run_command (ffmpeg_command )
148+
123149 if "Conversion failed" in ffmpeg_output :
124150 logger .error ("FFmpeg conversion test failed" )
125151 self .update_health_status (False )
@@ -138,14 +164,26 @@ def update_health_status(self, is_healthy):
138164 global health_status
139165 health_status ['healthy' ] = is_healthy
140166
141- async def run_command (self , command ):
167+ async def run_command (self , command , exclude_health_check = False ):
168+ binary = command .split ()[0 ].split ('/' )[- 1 ]
169+ if binary == 'ffmpeg' and not exclude_health_check :
170+ ffmpeg_gauge .inc ()
171+ elif binary == 'mediainfo' :
172+ mediainfo_counter .inc ()
173+ elif binary == 'ffprobe' :
174+ ffprobe_counter .inc ()
175+
142176 process = await asyncio .create_subprocess_shell (
143177 command ,
144178 stdout = asyncio .subprocess .PIPE ,
145179 stderr = asyncio .subprocess .PIPE
146180 )
147181
148182 stdout , stderr = await process .communicate ()
183+
184+ if binary == 'ffmpeg' and not exclude_health_check :
185+ ffmpeg_gauge .dec ()
186+
149187 output = stdout .decode ().strip () if stdout else ""
150188 error = stderr .decode ().strip () if stderr else ""
151189
@@ -199,8 +237,11 @@ async def health_check(request):
199237 else :
200238 return web .Response (text = "Health check failed" , status = 500 )
201239
240+ prometheus_app = make_asgi_app ()
202241 app = web .Application ()
203242 app .router .add_get ('/health' , health_check )
243+ app .router .add_route ('*' , '/metrics' , web ._run_app (prometheus_app ))
244+
204245 runner = web .AppRunner (app )
205246 await runner .setup ()
206247 site = web .TCPSite (runner , '0.0.0.0' , 8080 )
0 commit comments