perfrepo.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. # -*- coding: utf-8 -*-
  2. # pragma: no cover
  3. """
  4. (c) 2017 - Copyright Red Hat Inc
  5. Authors:
  6. Patrick Uiterwijk <puiterwijk@redhat.com>
  7. """
  8. from __future__ import absolute_import, print_function, unicode_literals
  9. import os
  10. import pprint
  11. import traceback
  12. import types
  13. import pygit2
  14. import six
  15. try:
  16. import _pygit2
  17. except ImportError:
  18. from pygit2 import _pygit2
  19. real_pygit2_repository = pygit2.Repository
  20. TOTALS = {"walks": 0, "steps": 0}
  21. REQUESTS = []
  22. STATS = {}
  23. class PerfRepoMeta(type): # pragma: no cover
  24. def __new__(cls, name, parents, dct):
  25. # create a class_id if it's not specified
  26. if "class_id" not in dct:
  27. dct["class_id"] = name.lower()
  28. # we need to call type.__new__ to complete the initialization
  29. return super(PerfRepoMeta, cls).__new__(cls, name, parents, dct)
  30. def __getattr__(cls, attr):
  31. real = getattr(real_pygit2_repository, attr)
  32. if type(real).__name__ in ["function", "builtin_function_or_method"]:
  33. def fake(*args, **kwargs):
  34. return real(*args, **kwargs)
  35. return fake
  36. else:
  37. return real
  38. class FakeWalker(six.Iterator): # pragma: no cover
  39. def __init__(self, parent):
  40. self.parent = parent
  41. self.wid = STATS["counters"]["walks"]
  42. STATS["counters"]["walks"] += 1
  43. STATS["walks"][self.wid] = {
  44. "steps": 0,
  45. "type": "walker",
  46. "init": traceback.extract_stack(limit=3)[0],
  47. "iter": None,
  48. }
  49. TOTALS["walks"] += 1
  50. def __getattr__(self, attr):
  51. return getattr(self.parent, attr)
  52. def __iter__(self):
  53. STATS["walks"][self.wid]["iter"] = traceback.extract_stack(limit=2)[0]
  54. return self
  55. def __next__(self):
  56. STATS["walks"][self.wid]["steps"] += 1
  57. TOTALS["steps"] += 1
  58. resp = next(iter(self.parent))
  59. return resp
  60. class FakeDiffHunk(object): # pragma: no cover
  61. def __init__(self, parent):
  62. self.parent = parent
  63. def __getattr__(self, attr):
  64. print("Getting Fake Hunk %s" % attr)
  65. resp = getattr(self.parent, attr)
  66. print("Response: %s" % resp)
  67. return resp
  68. class FakeDiffPatch(object): # pragma: no cover
  69. def __init__(self, parent):
  70. self.parent = parent
  71. def __getattr__(self, attr):
  72. if attr == "hunks":
  73. return [FakeDiffHunk(h) for h in self.parent.hunks]
  74. return getattr(self.parent, attr)
  75. class FakeDiffer(six.Iterator): # pragma: no cover
  76. def __init__(self, parent):
  77. self.parent = parent
  78. self.iter = None
  79. self.did = STATS["counters"]["diffs"]
  80. STATS["counters"]["diffs"] += 1
  81. STATS["diffs"][self.did] = {
  82. "init": traceback.extract_stack(limit=3)[0],
  83. "steps": 0,
  84. "iter": None,
  85. }
  86. def __getattr__(self, attr):
  87. return getattr(self.parent, attr)
  88. def __dir__(self):
  89. return dir(self.parent)
  90. def __iter__(self):
  91. STATS["diffs"][self.did]["iter"] = traceback.extract_stack(limit=2)[0]
  92. self.iter = iter(self.parent)
  93. return self
  94. def __next__(self):
  95. STATS["diffs"][self.did]["steps"] += 1
  96. resp = next(self.iter)
  97. if isinstance(resp, _pygit2.Patch):
  98. resp = FakeDiffPatch(resp)
  99. else:
  100. raise Exception("Unexpected %s returned from differ" % resp)
  101. return resp
  102. def __len__(self):
  103. return len(self.parent)
  104. class PerfRepo(
  105. six.with_metaclass(PerfRepoMeta, six.Iterator)
  106. ): # pragma: no cover
  107. """An utility class allowing to go around pygit2's inability to be
  108. stable.
  109. """
  110. def __init__(self, path):
  111. STATS["repo_inits"].append((path, traceback.extract_stack(limit=2)[0]))
  112. STATS["counters"]["inits"] += 1
  113. self.repo = real_pygit2_repository(path)
  114. self.iter = None
  115. def __getattr__(self, attr):
  116. real = getattr(self.repo, attr)
  117. if type(real) in [
  118. types.FunctionType,
  119. types.BuiltinFunctionType,
  120. types.BuiltinMethodType,
  121. ]:
  122. def fake(*args, **kwargs):
  123. resp = real(*args, **kwargs)
  124. if isinstance(resp, _pygit2.Walker):
  125. resp = FakeWalker(resp)
  126. elif isinstance(resp, _pygit2.Diff):
  127. resp = FakeDiffer(resp)
  128. return resp
  129. return fake
  130. elif isinstance(real, dict):
  131. real_getitem = real.__getitem__
  132. def fake_getitem(self, item):
  133. return real_getitem(item)
  134. real.__getitem__ = fake_getitem
  135. return real
  136. else:
  137. return real
  138. def __getitem__(self, item):
  139. return self.repo.__getitem__(item)
  140. def __contains__(self, item):
  141. return self.repo.__contains__(item)
  142. def __iter__(self):
  143. self.wid = STATS["counters"]["walks"]
  144. STATS["counters"]["walks"] += 1
  145. STATS["walks"][self.wid] = {
  146. "steps": 0,
  147. "type": "iter",
  148. "iter": traceback.extract_stack(limit=3)[0],
  149. }
  150. TOTALS["walks"] += 1
  151. self.iter = iter(self.repo)
  152. return self
  153. def __next__(self):
  154. STATS["walks"][self.wid]["steps"] += 1
  155. TOTALS["steps"] += 1
  156. return next(self.iter)
  157. if six.PY2:
  158. # Disable perfrepo on PY3, it doesn't work
  159. pygit2.Repository = PerfRepo
  160. def reset_stats(): # pragma: no cover
  161. """Resets STATS to be clear for the next request."""
  162. global STATS
  163. STATS = {
  164. "walks": {},
  165. "diffs": {},
  166. "repo_inits": [],
  167. "counters": {"walks": 0, "diffs": 0, "inits": 0},
  168. }
  169. # Make sure we start blank
  170. reset_stats()
  171. def print_stats(response): # pragma: no cover
  172. """Finalizes stats for the current request, and prints them possibly."""
  173. REQUESTS.append(STATS)
  174. if not os.environ.get("PAGURE_PERFREPO_VERBOSE"):
  175. return response
  176. print("Statistics:")
  177. pprint.pprint(STATS)
  178. return response