memprofile.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """Utility for dumping memory usage stats.
  2. This is tailored to mypy and knows (a little) about which list objects are
  3. owned by particular AST nodes, etc.
  4. """
  5. from __future__ import annotations
  6. import gc
  7. import sys
  8. from collections import defaultdict
  9. from typing import Dict, Iterable, cast
  10. from mypy.nodes import FakeInfo, Node
  11. from mypy.types import Type
  12. from mypy.util import get_class_descriptors
  13. def collect_memory_stats() -> tuple[dict[str, int], dict[str, int]]:
  14. """Return stats about memory use.
  15. Return a tuple with these items:
  16. - Dict from object kind to number of instances of that kind
  17. - Dict from object kind to total bytes used by all instances of that kind
  18. """
  19. objs = gc.get_objects()
  20. find_recursive_objects(objs)
  21. inferred = {}
  22. for obj in objs:
  23. if type(obj) is FakeInfo:
  24. # Processing these would cause a crash.
  25. continue
  26. n = type(obj).__name__
  27. if hasattr(obj, "__dict__"):
  28. # Keep track of which class a particular __dict__ is associated with.
  29. inferred[id(obj.__dict__)] = f"{n} (__dict__)"
  30. if isinstance(obj, (Node, Type)): # type: ignore[misc]
  31. if hasattr(obj, "__dict__"):
  32. for x in obj.__dict__.values():
  33. if isinstance(x, list):
  34. # Keep track of which node a list is associated with.
  35. inferred[id(x)] = f"{n} (list)"
  36. if isinstance(x, tuple):
  37. # Keep track of which node a list is associated with.
  38. inferred[id(x)] = f"{n} (tuple)"
  39. for k in get_class_descriptors(type(obj)):
  40. x = getattr(obj, k, None)
  41. if isinstance(x, list):
  42. inferred[id(x)] = f"{n} (list)"
  43. if isinstance(x, tuple):
  44. inferred[id(x)] = f"{n} (tuple)"
  45. freqs: dict[str, int] = {}
  46. memuse: dict[str, int] = {}
  47. for obj in objs:
  48. if id(obj) in inferred:
  49. name = inferred[id(obj)]
  50. else:
  51. name = type(obj).__name__
  52. freqs[name] = freqs.get(name, 0) + 1
  53. memuse[name] = memuse.get(name, 0) + sys.getsizeof(obj)
  54. return freqs, memuse
  55. def print_memory_profile(run_gc: bool = True) -> None:
  56. if not sys.platform.startswith("win"):
  57. import resource
  58. system_memuse = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
  59. else:
  60. system_memuse = -1 # TODO: Support this on Windows
  61. if run_gc:
  62. gc.collect()
  63. freqs, memuse = collect_memory_stats()
  64. print("%7s %7s %7s %s" % ("Freq", "Size(k)", "AvgSize", "Type"))
  65. print("-------------------------------------------")
  66. totalmem = 0
  67. i = 0
  68. for n, mem in sorted(memuse.items(), key=lambda x: -x[1]):
  69. f = freqs[n]
  70. if i < 50:
  71. print("%7d %7d %7.0f %s" % (f, mem // 1024, mem / f, n))
  72. i += 1
  73. totalmem += mem
  74. print()
  75. print("Mem usage RSS ", system_memuse // 1024)
  76. print("Total reachable ", totalmem // 1024)
  77. def find_recursive_objects(objs: list[object]) -> None:
  78. """Find additional objects referenced by objs and append them to objs.
  79. We use this since gc.get_objects() does not return objects without pointers
  80. in them such as strings.
  81. """
  82. seen = {id(o) for o in objs}
  83. def visit(o: object) -> None:
  84. if id(o) not in seen:
  85. objs.append(o)
  86. seen.add(id(o))
  87. for obj in objs.copy():
  88. if type(obj) is FakeInfo:
  89. # Processing these would cause a crash.
  90. continue
  91. if type(obj) in (dict, defaultdict):
  92. for key, val in cast(Dict[object, object], obj).items():
  93. visit(key)
  94. visit(val)
  95. if type(obj) in (list, tuple, set):
  96. for x in cast(Iterable[object], obj):
  97. visit(x)
  98. if hasattr(obj, "__slots__"):
  99. for base in type.mro(type(obj)):
  100. for slot in getattr(base, "__slots__", ()):
  101. if hasattr(obj, slot):
  102. visit(getattr(obj, slot))