1+ import datetime
12import os
23from logging import getLogger
34from pathlib import Path
45from tempfile import gettempdir
56from typing import Any
6-
77from taskiq .abc .middleware import TaskiqMiddleware
88from taskiq .message import TaskiqMessage
99from taskiq .result import TaskiqResult
@@ -43,7 +43,7 @@ def __init__(
4343 logger .debug ("Initializing metrics" )
4444
4545 try :
46- from prometheus_client import Counter , Histogram # noqa: PLC0415
46+ from prometheus_client import Counter , Histogram , Gauge # noqa: PLC0415
4747 except ImportError as exc :
4848 raise ImportError (
4949 "Cannot initialize metrics. Please install 'taskiq[metrics]'." ,
@@ -74,6 +74,24 @@ def __init__(
7474 "Time of function execution" ,
7575 ["task_name" ],
7676 )
77+
78+ self .in_flight_tasks = Gauge (
79+ "in_flight_tasks" ,
80+ "Number of tasks in flight" ,
81+ ["task_name" ],
82+ multiprocess_mode = "livesum" ,
83+ )
84+ self .queue_wait_seconds = Histogram (
85+ "queue_wait_seconds" ,
86+ "time task spent in message queue" ,
87+ ["task_name" ],
88+ )
89+ self .task_errors_by_type = Counter (
90+ "task_errors_by_type" ,
91+ "Number of errors raised in tasks by their type" ,
92+ ["task_name" , "error_type" ],
93+ )
94+
7795 self .server_port = server_port
7896 self .server_addr = server_addr
7997
@@ -104,6 +122,24 @@ def startup(self) -> None:
104122 except OSError as exc :
105123 logger .debug ("Cannot start prometheus server: %s" , exc )
106124
125+ def pre_send (
126+ self ,
127+ message : "TaskiqMessage" ,
128+ ) -> "TaskiqMessage" :
129+ """
130+ Function to track the time a task spend in queue.
131+
132+ This function tracks the time a task spends in a queue until it is executed.
133+
134+ :param message: current message.
135+ :return: message
136+ """
137+ if not message .labels .get ("_taskiq_enqueue_timestamp" ):
138+ message .labels ["_taskiq_enqueue_timestamp" ] = datetime .datetime .now (
139+ datetime .UTC ,
140+ ).isoformat () # Might conside using timezones too
141+ return message
142+
107143 def pre_execute (
108144 self ,
109145 message : "TaskiqMessage" ,
@@ -117,9 +153,41 @@ def pre_execute(
117153 :param message: current message.
118154 :return: message
119155 """
156+ if message .labels .get (
157+ "_taskiq_enqueue_timestamp" ,
158+ ): # Handle case where the sender doesn't use the prometheus middleware
159+ time_delta = datetime .datetime .now (
160+ datetime .UTC ,
161+ ) - datetime .datetime .fromisoformat (
162+ message .labels ["_taskiq_enqueue_timestamp" ],
163+ )
164+ time_delta = max (0 , time_delta .total_seconds ())
165+ self .queue_wait_seconds .labels (message .task_name ).observe (
166+ time_delta ,
167+ )
168+
169+ self .in_flight_tasks .labels (message .task_name ).inc ()
120170 self .received_tasks .labels (message .task_name ).inc ()
121171 return message
122172
173+ def on_error (
174+ self ,
175+ message : TaskiqMessage ,
176+ result : TaskiqResult [Any ], # pylint: disable=unused-argument
177+ exception : BaseException ,
178+ ) -> None :
179+ """
180+ This function tracks the number of errors raised by tasks.
181+
182+ :param message: the received task message
183+ :param result: the result of task
184+ :param exception: exception raised
185+ """
186+ self .task_errors_by_type .labels (
187+ message .task_name ,
188+ type (exception ).__name__ ,
189+ ).inc ()
190+
123191 def post_execute (
124192 self ,
125193 message : "TaskiqMessage" ,
@@ -135,6 +203,7 @@ def post_execute(
135203 self .found_errors .labels (message .task_name ).inc ()
136204 else :
137205 self .success_tasks .labels (message .task_name ).inc ()
206+ self .in_flight_tasks .labels (message .task_name ).dec ()
138207 self .execution_time .labels (message .task_name ).observe (result .execution_time )
139208
140209 def post_save (
0 commit comments