Skip to content

Commit

Permalink
Updated middleware and views to match upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
passy committed Aug 19, 2011
1 parent 46e6b79 commit a4dfcc9
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 35 deletions.
36 changes: 24 additions & 12 deletions flaskext/gae_mini_profiler/__init__.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -146,18 +146,30 @@ def _render(self, template_name, context):
def _request_view(self): def _request_view(self):
"""Renders the request stats.""" """Renders the request stats."""


request_id = request.args['request_id'] request_ids = request.args['request_ids']
stats = profiler.RequestStats.get(request_id)

stats_list = []
if not stats: for request_id in request_ids.split(','):
return u'' request_stats = profiler.RequestStats.get(request_id)


dict_request_stats = {} if request_stats and not request_stats.disabled:
for property in profiler.RequestStats.serialized_properties: dict_request_stats = {}
dict_request_stats[property] = \ for property in profiler.RequestStats.serialized_properties:
stats.__getattribute__(property) dict_request_stats[property] = \

request_stats.__getattribute__(property)
return jsonify(dict_request_stats)
stats_list.append(dict_request_stats)

# Don't show temporary redirect profiles more than once
# automatically, as they are tied to URL params and may be
# copied around easily.
if request_stats.temporary_redirect:
request_stats.disabled = True
request_stats.store()

# For security reasons, we return an object instead of a list as it is
# dont in the upstream module.
return jsonify(stats=stats_list)


def _share_view(self): def _share_view(self):
"""Renders the shared stats view.""" """Renders the shared stats view."""
Expand Down
97 changes: 82 additions & 15 deletions flaskext/gae_mini_profiler/profiler.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
:license: MIT, see LICENSE for more details :license: MIT, see LICENSE for more details
""" """



import datetime import datetime
import logging
import os import os
import pickle import pickle
import re
import simplejson import simplejson
import StringIO import StringIO
import sys
from types import GeneratorType from types import GeneratorType
import zlib import zlib


Expand All @@ -24,26 +22,43 @@
# request_id is a per-request identifier accessed by a couple other pieces of gae_mini_profiler # request_id is a per-request identifier accessed by a couple other pieces of gae_mini_profiler
request_id = None request_id = None



class RequestStatsHandler(RequestHandler): class RequestStatsHandler(RequestHandler):


def get(self): def get(self):


self.response.headers["Content-Type"] = "application/json" self.response.headers["Content-Type"] = "application/json"


request_stats = RequestStats.get(self.request.get("request_id")) list_request_ids = []
if not request_stats:
return request_ids = self.request.get("request_ids")
if request_ids:
list_request_ids = request_ids.split(",")

list_request_stats = []

for request_id in list_request_ids:

request_stats = RequestStats.get(request_id)


dict_request_stats = {} if request_stats and not request_stats.disabled:
for property in RequestStats.serialized_properties:
dict_request_stats[property] = request_stats.__getattribute__(property)


self.response.out.write(simplejson.dumps(dict_request_stats)) dict_request_stats = {}
for property in RequestStats.serialized_properties:
dict_request_stats[property] = request_stats.__getattribute__(property)

list_request_stats.append(dict_request_stats)

# Don't show temporary redirect profiles more than once automatically, as they are
# tied to URL params and may be copied around easily.
if request_stats.temporary_redirect:
request_stats.disabled = True
request_stats.store()

self.response.out.write(simplejson.dumps(list_request_stats))


class RequestStats(object): class RequestStats(object):


serialized_properties = ["request_id", "url", "url_short", "s_dt", "profiler_results", "appstats_results"] serialized_properties = ["request_id", "url", "url_short", "s_dt", "profiler_results", "appstats_results", "temporary_redirect"]


def __init__(self, request_id, environ, middleware): def __init__(self, request_id, environ, middleware):
self.request_id = request_id self.request_id = request_id
Expand All @@ -61,6 +76,9 @@ def __init__(self, request_id, environ, middleware):
self.profiler_results = RequestStats.calc_profiler_results(middleware) self.profiler_results = RequestStats.calc_profiler_results(middleware)
self.appstats_results = RequestStats.calc_appstats_results(middleware) self.appstats_results = RequestStats.calc_appstats_results(middleware)


self.temporary_redirect = middleware.temporary_redirect
self.disabled = False

def store(self): def store(self):
# Store compressed results so we stay under the memcache 1MB limit # Store compressed results so we stay under the memcache 1MB limit
pickled = pickle.dumps(self) pickled = pickle.dumps(self)
Expand Down Expand Up @@ -102,7 +120,9 @@ def short_method_fmt(s):
def short_rpc_file_fmt(s): def short_rpc_file_fmt(s):
if not s: if not s:
return "" return ""
return s[s.find("/"):] if "/" in s:
return s[s.find("/"):]
return s


@staticmethod @staticmethod
def calc_profiler_results(middleware): def calc_profiler_results(middleware):
Expand Down Expand Up @@ -155,15 +175,22 @@ def calc_appstats_results(middleware):
calls = [] calls = []
service_totals_dict = {} service_totals_dict = {}
likely_dupes = False likely_dupes = False
end_offset_last = 0


dict_requests = {} dict_requests = {}


appstats_key = long(middleware.recorder.start_timestamp * 1000) appstats_key = long(middleware.recorder.start_timestamp * 1000)


for trace in middleware.recorder.traces: for trace in middleware.recorder.traces:
total_call_count += 1 total_call_count += 1

total_time += trace.duration_milliseconds() total_time += trace.duration_milliseconds()


# Don't accumulate total RPC time for traces that overlap asynchronously
if trace.start_offset_milliseconds() < end_offset_last:
total_time -= (end_offset_last - trace.start_offset_milliseconds())
end_offset_last = trace.start_offset_milliseconds() + trace.duration_milliseconds()

service_prefix = trace.service_call_name() service_prefix = trace.service_call_name()


if "." in service_prefix: if "." in service_prefix:
Expand Down Expand Up @@ -232,6 +259,7 @@ def __init__(self, app):
self.app_clean = app self.app_clean = app
self.prof = None self.prof = None
self.recorder = None self.recorder = None
self.temporary_redirect = False


def __call__(self, environ, start_response): def __call__(self, environ, start_response):


Expand All @@ -242,18 +270,28 @@ def __call__(self, environ, start_response):
self.app = self.app_clean self.app = self.app_clean
self.prof = None self.prof = None
self.recorder = None self.recorder = None
self.temporary_redirect = False


if self.should_profile(environ): if self.should_profile(environ):


# Set a random ID for this request so we can look up stats later # Set a random ID for this request so we can look up stats later
import base64 import base64
import os request_id = base64.urlsafe_b64encode(os.urandom(5))
request_id = base64.urlsafe_b64encode(os.urandom(15))


# Send request id in headers so jQuery ajax calls can pick # Send request id in headers so jQuery ajax calls can pick
# up profiles. # up profiles.
def profiled_start_response(status, headers, exc_info = None): def profiled_start_response(status, headers, exc_info = None):

if status.startswith("302 "):
# Temporary redirect. Add request identifier to redirect location
# so next rendered page can show this request's profile.
headers = ProfilerWSGIMiddleware.headers_with_modified_redirect(environ, headers)
self.temporary_redirect = True

# Append headers used when displaying profiler results from ajax requests
headers.append(("X-MiniProfiler-Id", request_id)) headers.append(("X-MiniProfiler-Id", request_id))
headers.append(("X-MiniProfiler-QS", environ.get("QUERY_STRING")))

return start_response(status, headers, exc_info) return start_response(status, headers, exc_info)


# Configure AppStats output, keeping a high level of request # Configure AppStats output, keeping a high level of request
Expand Down Expand Up @@ -303,3 +341,32 @@ def wrapped_appstats_app(environ, start_response):
result = self.app(environ, start_response) result = self.app(environ, start_response)
for value in result: for value in result:
yield value yield value

@staticmethod
def headers_with_modified_redirect(environ, headers):
headers_modified = []

for header in headers:
if header[0] == "Location":
reg = re.compile("mp-r-id=([^&]+)")

# Keep any chain of redirects around
request_id_chain = request_id
match = reg.search(environ.get("QUERY_STRING"))
if match:
request_id_chain = ",".join([match.groups()[0], request_id])

# Remove any pre-existing miniprofiler redirect id
location = header[1]
location = reg.sub("", location)

# Add current request id as miniprofiler redirect id
location += ("&" if "?" in location else "?")
location = location.replace("&&", "&")
location += "mp-r-id=%s" % request_id_chain

headers_modified.append((header[0], location))
else:
headers_modified.append(header)

return headers_modified
6 changes: 3 additions & 3 deletions flaskext/gae_mini_profiler/static/js/profiler.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 13 additions & 5 deletions tests/extension.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -165,19 +165,27 @@ def test_request_view(self):
from flaskext.gae_mini_profiler import GAEMiniProfiler from flaskext.gae_mini_profiler import GAEMiniProfiler
properties = ['a', 'b', 'c'] properties = ['a', 'b', 'c']


request_id = mock.Sentinel() request_ids = ['1', '2', '3']
request.args = {'request_id': request_id} request.args = {'request_ids': ','.join(request_ids)}


stats = profiler.RequestStats.get.return_value stats = profiler.RequestStats.get.return_value
stats.__getattribute__ = lambda x: x stats.__getattribute__ = lambda x: x
stats.disabled = False
stats.temporary_redirect = False

profiler.RequestStats.serialized_properties = properties profiler.RequestStats.serialized_properties = properties
app = mock.Mock() app = mock.Mock()
ext = GAEMiniProfiler(app) ext = GAEMiniProfiler(app)
ext._request_view() ext._request_view()


profiler.RequestStats.get.assertCalledOnceWith(request_id) self.assertEquals(len(request_ids),
jsonify.assert_called_once_with( profiler.RequestStats.get.call_count)
dict(zip(properties, properties)))
expected_result = []
for id in request_ids:
expected_result.append(dict(zip(properties, properties)))

jsonify.assert_called_once_with(stats=expected_result)
finally: finally:
request_patcher.stop() request_patcher.stop()
profiler_patcher.stop() profiler_patcher.stop()
Expand Down

0 comments on commit a4dfcc9

Please sign in to comment.