gclogger.py 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. from __future__ import annotations
  2. import gc
  3. import time
  4. from typing import Mapping
  5. class GcLogger:
  6. """Context manager to log GC stats and overall time."""
  7. def __enter__(self) -> GcLogger:
  8. self.gc_start_time: float | None = None
  9. self.gc_time = 0.0
  10. self.gc_calls = 0
  11. self.gc_collected = 0
  12. self.gc_uncollectable = 0
  13. gc.callbacks.append(self.gc_callback)
  14. self.start_time = time.time()
  15. return self
  16. def gc_callback(self, phase: str, info: Mapping[str, int]) -> None:
  17. if phase == "start":
  18. assert self.gc_start_time is None, "Start phase out of sequence"
  19. self.gc_start_time = time.time()
  20. elif phase == "stop":
  21. assert self.gc_start_time is not None, "Stop phase out of sequence"
  22. self.gc_calls += 1
  23. self.gc_time += time.time() - self.gc_start_time
  24. self.gc_start_time = None
  25. self.gc_collected += info["collected"]
  26. self.gc_uncollectable += info["uncollectable"]
  27. else:
  28. assert False, f"Unrecognized gc phase ({phase!r})"
  29. def __exit__(self, *args: object) -> None:
  30. while self.gc_callback in gc.callbacks:
  31. gc.callbacks.remove(self.gc_callback)
  32. def get_stats(self) -> Mapping[str, float]:
  33. end_time = time.time()
  34. result = {}
  35. result["gc_time"] = self.gc_time
  36. result["gc_calls"] = self.gc_calls
  37. result["gc_collected"] = self.gc_collected
  38. result["gc_uncollectable"] = self.gc_uncollectable
  39. result["build_time"] = end_time - self.start_time
  40. return result