Last active
August 23, 2018 14:29
-
-
Save yumike/7b69afb5a1206b0b8db6458cc88fd038 to your computer and use it in GitHub Desktop.
cian-prof
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding: utf-8 | |
import re | |
from collections import defaultdict, deque | |
from statsd.client import StatsClient, Timer | |
class Prof(object): | |
"""Инструмент для профилирования времени исполнения кода в контексте | |
HTTP-запроса или команды. | |
Отправляет в statsd метрики вида:: | |
{app}.{method}.{handler}.{status_code}.{stat} | |
``method`` - метод HTTP (get/post/put/etc). | |
``handler`` - нормализованное (точки -> минусы) имя представления. | |
``status_code`` - код ответа сервера. | |
``stat`` - имя метрики. | |
""" | |
INVALID_CHAR_RE = re.compile(r'[^0-9a-zA-Z_]') | |
def __init__(self, app, statsd=None): | |
self.app = app | |
self.statsd = statsd or StatsClient() | |
self.timings = None | |
self.orig_view_name = None | |
self.view_name = None | |
self.status_code = None | |
self.is_request = None | |
def _get_stat_name(self, stat): | |
return '.'.join(( | |
self.app, | |
self.view_name, | |
str(self.status_code), | |
self.INVALID_CHAR_RE.sub('-', stat), | |
)) | |
def timer(self, stat, rate=1): | |
return Timer(self, stat, rate) | |
def timing(self, stat, delta, rate=1): | |
if self.is_request: | |
self.timings.append((stat, delta)) | |
def set_view_name(self, method, name): | |
self.orig_view_name = name | |
self.view_name = '.'.join(( | |
method.lower(), | |
self.INVALID_CHAR_RE.sub('-', name), | |
)) | |
def set_status_code(self, status_code): | |
self.status_code = status_code | |
def start_request(self): | |
self.is_request = True | |
self.timings = deque() | |
def finish_request(self): | |
if not (self.is_request and self.view_name and self.status_code): | |
return | |
counts = defaultdict(int) | |
cumulative = defaultdict(int) | |
try: | |
with self.statsd.pipeline() as pipe: | |
while self.timings: | |
stat, delta = self.timings.popleft() | |
cumulative[stat] += delta | |
counts[stat] += 1 | |
for stat, delta in cumulative.iteritems(): | |
pipe.timing(self._get_stat_name(stat), delta) | |
for stat, count in counts.iteritems(): | |
pipe.incr(self._get_stat_name(stat), count) | |
finally: | |
self.is_request = None | |
self.view_name = None | |
self.status_code = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from cian_prof.base import Prof | |
prof = Prof('app') | |
class ProfRequestMiddleware(object): | |
def process_request(self, request): | |
prof.start_request() | |
request._prof_request_timer = prof.timer('request') | |
request._prof_request_timer.start() | |
def process_response(self, request, response): | |
if hasattr(request, '_prof_request_timer'): | |
request._prof_request_timer.stop() | |
resolver_match = getattr(request, 'resolver_match', None) | |
if resolver_match and getattr(resolver_match, 'url_name', None): | |
prof.set_view_name(request.method, resolver_match.url_name) | |
prof.set_status_code(response.status_code) | |
prof.finish_request() | |
return response | |
class ProfViewMiddleware(object): | |
def process_view(self, request, *args, **kwargs): | |
request._prof_view_timer = prof.timer('view') | |
request._prof_view_timer.start() | |
def process_response(self, request, response): | |
if hasattr(request, '_prof_view_timer'): | |
request._prof_view_timer.stop() | |
return response |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment