Skip to content

Instantly share code, notes, and snippets.

@yumike
Last active August 23, 2018 14:29
Show Gist options
  • Save yumike/7b69afb5a1206b0b8db6458cc88fd038 to your computer and use it in GitHub Desktop.
Save yumike/7b69afb5a1206b0b8db6458cc88fd038 to your computer and use it in GitHub Desktop.
cian-prof
# 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
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