# -*- coding: utf-8 -*-
# pragma: no cover
"""
(c) 2017 - Copyright Red Hat Inc
Authors:
Patrick Uiterwijk <puiterwijk@redhat.com>
"""
from __future__ import print_function, unicode_literals, absolute_import
import pprint
import os
import traceback
import types
import six
import pygit2
import _pygit2
real_pygit2_repository = pygit2.Repository
TOTALS = {"walks": 0, "steps": 0}
REQUESTS = []
STATS = {}
class PerfRepoMeta(type): # pragma: no cover
def __new__(cls, name, parents, dct):
# create a class_id if it's not specified
if "class_id" not in dct:
dct["class_id"] = name.lower()
# we need to call type.__new__ to complete the initialization
return super(PerfRepoMeta, cls).__new__(cls, name, parents, dct)
def __getattr__(cls, attr):
real = getattr(real_pygit2_repository, attr)
if type(real).__name__ in ["function", "builtin_function_or_method"]:
def fake(*args, **kwargs):
return real(*args, **kwargs)
return fake
else:
return real
class FakeWalker(six.Iterator): # pragma: no cover
def __init__(self, parent):
self.parent = parent
self.wid = STATS["counters"]["walks"]
STATS["counters"]["walks"] += 1
STATS["walks"][self.wid] = {
"steps": 0,
"type": "walker",
"init": traceback.extract_stack(limit=3)[0],
"iter": None,
}
TOTALS["walks"] += 1
def __getattr__(self, attr):
return getattr(self.parent, attr)
def __iter__(self):
STATS["walks"][self.wid]["iter"] = traceback.extract_stack(limit=2)[0]
return self
def __next__(self):
STATS["walks"][self.wid]["steps"] += 1
TOTALS["steps"] += 1
resp = next(iter(self.parent))
return resp
class FakeDiffHunk(object): # pragma: no cover
def __init__(self, parent):
self.parent = parent
def __getattr__(self, attr):
print("Getting Fake Hunk %s" % attr)
resp = getattr(self.parent, attr)
print("Response: %s" % resp)
return resp
class FakeDiffPatch(object): # pragma: no cover
def __init__(self, parent):
self.parent = parent
def __getattr__(self, attr):
if attr == "hunks":
return [FakeDiffHunk(h) for h in self.parent.hunks]
return getattr(self.parent, attr)
class FakeDiffer(six.Iterator): # pragma: no cover
def __init__(self, parent):
self.parent = parent
self.iter = None
self.did = STATS["counters"]["diffs"]
STATS["counters"]["diffs"] += 1
STATS["diffs"][self.did] = {
"init": traceback.extract_stack(limit=3)[0],
"steps": 0,
"iter": None,
}
def __getattr__(self, attr):
return getattr(self.parent, attr)
def __dir__(self):
return dir(self.parent)
def __iter__(self):
STATS["diffs"][self.did]["iter"] = traceback.extract_stack(limit=2)[0]
self.iter = iter(self.parent)
return self
def __next__(self):
STATS["diffs"][self.did]["steps"] += 1
resp = next(self.iter)
if isinstance(resp, _pygit2.Patch):
resp = FakeDiffPatch(resp)
else:
raise Exception("Unexpected %s returned from differ" % resp)
return resp
def __len__(self):
return len(self.parent)
class PerfRepo(
six.with_metaclass(PerfRepoMeta, six.Iterator)
): # pragma: no cover
""" An utility class allowing to go around pygit2's inability to be
stable.
"""
def __init__(self, path):
STATS["repo_inits"].append((path, traceback.extract_stack(limit=2)[0]))
STATS["counters"]["inits"] += 1
self.repo = real_pygit2_repository(path)
self.iter = None
def __getattr__(self, attr):
real = getattr(self.repo, attr)
if type(real) in [
types.FunctionType,
types.BuiltinFunctionType,
types.BuiltinMethodType,
]:
def fake(*args, **kwargs):
resp = real(*args, **kwargs)
if isinstance(resp, _pygit2.Walker):
resp = FakeWalker(resp)
elif isinstance(resp, _pygit2.Diff):
resp = FakeDiffer(resp)
return resp
return fake
elif isinstance(real, dict):
real_getitem = real.__getitem__
def fake_getitem(self, item):
return real_getitem(item)
real.__getitem__ = fake_getitem
return real
else:
return real
def __getitem__(self, item):
return self.repo.__getitem__(item)
def __contains__(self, item):
return self.repo.__contains__(item)
def __iter__(self):
self.wid = STATS["counters"]["walks"]
STATS["counters"]["walks"] += 1
STATS["walks"][self.wid] = {
"steps": 0,
"type": "iter",
"iter": traceback.extract_stack(limit=3)[0],
}
TOTALS["walks"] += 1
self.iter = iter(self.repo)
return self
def __next__(self):
STATS["walks"][self.wid]["steps"] += 1
TOTALS["steps"] += 1
return next(self.iter)
if six.PY2:
# Disable perfrepo on PY3, it doesn't work
pygit2.Repository = PerfRepo
def reset_stats(): # pragma: no cover
"""Resets STATS to be clear for the next request."""
global STATS
STATS = {
"walks": {},
"diffs": {},
"repo_inits": [],
"counters": {"walks": 0, "diffs": 0, "inits": 0},
}
# Make sure we start blank
reset_stats()
def print_stats(response): # pragma: no cover
"""Finalizes stats for the current request, and prints them possibly."""
REQUESTS.append(STATS)
if not os.environ.get("PAGURE_PERFREPO_VERBOSE"):
return response
print("Statistics:")
pprint.pprint(STATS)
return response