22import time
33from typing import TYPE_CHECKING , Optional
44
5- from ._internal import g_fork_lock
5+ from ._internal import g_fork_lock , import_attribute
66
77if TYPE_CHECKING :
88 from .worker import Worker
99
1010
11- class StatsThread (threading .Thread ):
12- def __init__ (self , tiger : "Worker" ) -> None :
13- super (StatsThread , self ).__init__ ()
11+ class Stats :
12+ def __init__ (self , tiger , callback = None ) -> None :
13+ super ().__init__ ()
14+
1415 self .tiger = tiger
15- self ._stop_event = threading .Event ()
1616
17- self ._task_running = False
17+ self ._logging_thread : Optional [StatsLoggingThread ] = None
18+
1819 self ._time_start = time .monotonic ()
19- self ._time_busy : float = 0.0
20- self ._task_start_time : Optional [float ] = None
21- self .daemon = True # Exit process if main thread exits unexpectedly
20+ self ._time_busy = 0
21+ self ._task_start_time = None
2222
2323 # Lock that protects stats computations from interleaving. For example,
2424 # we don't want report_task_start() to run at the same time as
25- # compute_stats (), as it might result in an inconsistent state.
25+ # compute (), as it might result in an inconsistent state.
2626 self ._computation_lock = threading .Lock ()
2727
28- def report_task_start (self ) -> None :
28+ # Callback to receive the duration of each completed task.
29+ self ._callback = (
30+ import_attribute (callback )
31+ if (callback := self .tiger .config ["STATS_CALLBACK" ])
32+ else None
33+ )
34+
35+ def report_task_start (self ):
2936 now = time .monotonic ()
3037 with self ._computation_lock :
3138 self ._task_start_time = now
32- self ._task_running = True
3339
3440 def report_task_end (self ) -> None :
3541 now = time .monotonic ()
42+
43+ if self ._callback :
44+ self ._callback (now - self ._task_start_time )
45+
3646 with self ._computation_lock :
37- assert self ._task_start_time is not None
38- self ._time_busy += now - self ._task_start_time
39- self ._task_running = False
47+ self ._record_time_busy (now )
4048 self ._task_start_time = None
4149
42- def compute_stats (self ) -> None :
50+ def log (self ) -> None :
4351 now = time .monotonic ()
4452
4553 with self ._computation_lock :
4654 time_total = now - self ._time_start
4755 time_busy = self ._time_busy
56+
57+ if self ._task_start_time is not None :
58+ # Add busy time for the currently running task
59+ self ._record_time_busy (now )
60+
61+ time_busy = self ._time_busy
62+
4863 self ._time_start = now
4964 self ._time_busy = 0
50- if self ._task_running :
51- assert self ._task_start_time is not None
52- time_busy += now - self ._task_start_time
53- self ._task_start_time = now
54- else :
55- self ._task_start_time = None
5665
5766 if time_total :
5867 utilization = 100.0 / time_total * time_busy
@@ -64,9 +73,33 @@ def compute_stats(self) -> None:
6473 utilization = utilization ,
6574 )
6675
76+ def start_logging_thread (self ):
77+ if not self ._logging_thread :
78+ self ._logging_thread = StatsLoggingThread (self )
79+ self ._logging_thread .start ()
80+
81+ def stop_logging_thread (self ):
82+ if self ._logging_thread :
83+ self ._logging_thread .stop ()
84+ self ._logging_thread = None
85+
86+ def _record_time_busy (self , now : float ) -> None :
87+ self ._time_busy += now - max (self ._task_start_time , self ._time_start )
88+
89+
90+ class StatsLoggingThread (threading .Thread ):
91+ def __init__ (self , stats ) -> None :
92+ super ().__init__ ()
93+
94+ self .tiger = stats .tiger
95+ self ._stats : Stats = stats
96+ self ._stop_event = threading .Event ()
97+
98+ self .daemon = True # Exit process if main thread exits unexpectedly
99+
67100 def run (self ) -> None :
68101 while not self ._stop_event .wait (self .tiger .config ["STATS_INTERVAL" ]):
69- self .compute_stats ()
102+ self ._stats . log ()
70103
71104 def stop (self ) -> None :
72105 self ._stop_event .set ()
0 commit comments