perfrepo.py 5.6 KB

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