47 lines
1.6 KiB
Python
47 lines
1.6 KiB
Python
|
import gc
|
||
|
import time
|
||
|
|
||
|
from typing import Mapping, Optional
|
||
|
|
||
|
|
||
|
class GcLogger:
|
||
|
"""Context manager to log GC stats and overall time."""
|
||
|
|
||
|
def __enter__(self) -> 'GcLogger':
|
||
|
self.gc_start_time: Optional[float] = None
|
||
|
self.gc_time = 0.0
|
||
|
self.gc_calls = 0
|
||
|
self.gc_collected = 0
|
||
|
self.gc_uncollectable = 0
|
||
|
gc.callbacks.append(self.gc_callback)
|
||
|
self.start_time = time.time()
|
||
|
return self
|
||
|
|
||
|
def gc_callback(self, phase: str, info: Mapping[str, int]) -> None:
|
||
|
if phase == 'start':
|
||
|
assert self.gc_start_time is None, "Start phase out of sequence"
|
||
|
self.gc_start_time = time.time()
|
||
|
elif phase == 'stop':
|
||
|
assert self.gc_start_time is not None, "Stop phase out of sequence"
|
||
|
self.gc_calls += 1
|
||
|
self.gc_time += time.time() - self.gc_start_time
|
||
|
self.gc_start_time = None
|
||
|
self.gc_collected += info['collected']
|
||
|
self.gc_uncollectable += info['uncollectable']
|
||
|
else:
|
||
|
assert False, f"Unrecognized gc phase ({phase!r})"
|
||
|
|
||
|
def __exit__(self, *args: object) -> None:
|
||
|
while self.gc_callback in gc.callbacks:
|
||
|
gc.callbacks.remove(self.gc_callback)
|
||
|
|
||
|
def get_stats(self) -> Mapping[str, float]:
|
||
|
end_time = time.time()
|
||
|
result = {}
|
||
|
result['gc_time'] = self.gc_time
|
||
|
result['gc_calls'] = self.gc_calls
|
||
|
result['gc_collected'] = self.gc_collected
|
||
|
result['gc_uncollectable'] = self.gc_uncollectable
|
||
|
result['build_time'] = end_time - self.start_time
|
||
|
return result
|