11import threading
22import time
3- from typing import TYPE_CHECKING , Optional
3+ from typing import TYPE_CHECKING , Callable , Optional
44
5- from ._internal import g_fork_lock
5+ from ._internal import g_fork_lock , import_attribute
66
77if TYPE_CHECKING :
8- from .worker import Worker
8+ from .tasktiger import TaskTiger
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__ (
13+ self ,
14+ tiger : "TaskTiger" ,
15+ callback : Optional [Callable [[float ], None ]] = None ,
16+ ) -> None :
17+ super ().__init__ ()
18+
1419 self .tiger = tiger
15- self ._stop_event = threading .Event ()
1620
17- self ._task_running = False
21+ self ._logging_thread : Optional [StatsLoggingThread ] = None
22+
1823 self ._time_start = time .monotonic ()
19- self ._time_busy : float = 0.0
24+ self ._time_busy = 0.0
2025 self ._task_start_time : Optional [float ] = None
21- self .daemon = True # Exit process if main thread exits unexpectedly
2226
2327 # Lock that protects stats computations from interleaving. For example,
2428 # 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.
26- self ._computation_lock = threading .Lock ()
29+ # log(), as it might result in an inconsistent state.
30+ self ._lock = threading .Lock ()
31+
32+ # Callback to receive the duration of each completed task.
33+ self ._callback = (
34+ import_attribute (callback )
35+ if (callback := self .tiger .config ["STATS_CALLBACK" ])
36+ else None
37+ )
2738
2839 def report_task_start (self ) -> None :
2940 now = time .monotonic ()
30- with self ._computation_lock :
41+ with self ._lock :
3142 self ._task_start_time = now
32- self ._task_running = True
3343
3444 def report_task_end (self ) -> None :
45+ assert self ._task_start_time
3546 now = time .monotonic ()
36- 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+
48+ if self ._callback :
49+ self ._callback (now - self ._task_start_time )
50+
51+ with self ._lock :
52+ self ._record_time_busy (now )
4053 self ._task_start_time = None
4154
42- def compute_stats (self ) -> None :
55+ def log (self ) -> None :
4356 now = time .monotonic ()
4457
45- with self ._computation_lock :
58+ with self ._lock :
4659 time_total = now - self ._time_start
4760 time_busy = self ._time_busy
61+
62+ if self ._task_start_time is not None :
63+ # Add busy time for the currently running task
64+ self ._record_time_busy (now )
65+
66+ time_busy = self ._time_busy
67+
4868 self ._time_start = now
4969 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
5670
5771 if time_total :
5872 utilization = 100.0 / time_total * time_busy
@@ -64,9 +78,34 @@ def compute_stats(self) -> None:
6478 utilization = utilization ,
6579 )
6680
81+ def start_logging_thread (self ) -> None :
82+ if not self ._logging_thread :
83+ self ._logging_thread = StatsLoggingThread (self )
84+ self ._logging_thread .start ()
85+
86+ def stop_logging_thread (self ) -> None :
87+ if self ._logging_thread :
88+ self ._logging_thread .stop ()
89+ self ._logging_thread = None
90+
91+ def _record_time_busy (self , now : float ) -> None :
92+ assert self ._task_start_time
93+ self ._time_busy += now - max (self ._task_start_time , self ._time_start )
94+
95+
96+ class StatsLoggingThread (threading .Thread ):
97+ def __init__ (self , stats : Stats ) -> None :
98+ super ().__init__ ()
99+
100+ self .tiger = stats .tiger
101+ self ._stats : Stats = stats
102+ self ._stop_event = threading .Event ()
103+
104+ self .daemon = True # Exit process if main thread exits unexpectedly
105+
67106 def run (self ) -> None :
68107 while not self ._stop_event .wait (self .tiger .config ["STATS_INTERVAL" ]):
69- self .compute_stats ()
108+ self ._stats . log ()
70109
71110 def stop (self ) -> None :
72111 self ._stop_event .set ()
0 commit comments