[Repoze-checkins] r704 - in repoze.profile/trunk: . repoze/profile
Chris McDonough
chrism at agendaless.com
Wed Feb 20 14:12:05 UTC 2008
Author: Chris McDonough <chrism at agendaless.com>
Date: Wed Feb 20 09:12:04 2008
New Revision: 704
Log:
Add a UI.
Added:
repoze.profile/trunk/repoze/profile/profiler.html
repoze.profile/trunk/repoze/profile/tests.py (contents, props changed)
Modified:
repoze.profile/trunk/repoze/profile/__init__.py
repoze.profile/trunk/repoze/profile/profiler.py
repoze.profile/trunk/setup.py
Modified: repoze.profile/trunk/repoze/profile/__init__.py
==============================================================================
--- repoze.profile/trunk/repoze/profile/__init__.py (original)
+++ repoze.profile/trunk/repoze/profile/__init__.py Wed Feb 20 09:12:04 2008
@@ -1,9 +1 @@
-from paste.debug.profile import profile_decorator
-
-class OKView:
- def __init__(self, context, request):
- self.context = context
- self.request = request
-
- def __call__(self):
- return 'OK'
+# a module
Added: repoze.profile/trunk/repoze/profile/profiler.html
==============================================================================
--- (empty file)
+++ repoze.profile/trunk/repoze/profile/profiler.html Wed Feb 20 09:12:04 2008
@@ -0,0 +1,67 @@
+<html xmlns:meld="http://www.plope.com/software/meld3">
+ <head>
+ </head>
+ <body>
+
+ <div meld:id="description" class="form-text">
+ There is not yet any profiling data to report.
+ </div>
+
+ <div meld:id="formelements">
+ <form action="." meld:id="form" method="POST">
+ <table>
+ <tr>
+ <td>
+ <strong>Sort</strong>:
+ <select name="sort" meld:id="sort">
+ <option value="time">time</option>
+ <option value="cumulative">cumulative</option>
+ <option value="calls">calls</option>
+ <option value="pcalls">pcalls</option>
+ <option value="name">name</option>
+ <option value="file">file</option>
+ <option value="module">module</option>
+ <option value="line">line</option>
+ <option value="nfl">nfl</option>
+ <option value="stdname">stdname</option>
+ </select>
+ </td>
+ <td>
+ <strong>Limit</strong>:
+ <select name="limit" meld:id="limit">
+ <option value="100">100</option>
+ <option value="200">200</option>
+ <option value="300">300</option>
+ <option value="400">400</option>
+ <option value="500">500</option>
+ </select>
+ </td>
+ <td>
+ <strong>Full Dirs</strong>:
+ <input type="checkbox" name="full_dirs" value="1"
+ meld:id="full_dirs"/>
+ </td>
+ <td>
+ <strong>Mode</strong>:
+ <select name="mode" meld:id="mode">
+ <option value="stats">stats</option>
+ <option value="callees">callees</option>
+ <option value="callers">callers</option>
+ </select>
+ </td>
+ <td>
+ <input type="submit" name="submit" value="Update"/>
+ </td>
+ <td>
+ <input type="submit" name="clear" value="Clear"/>
+ </td>
+ </tr>
+ </table>
+ </form>
+ </div>
+ <pre meld:id="profiledata">
+ </pre>
+ </body>
+</html>
+
+
Modified: repoze.profile/trunk/repoze/profile/profiler.py
==============================================================================
--- repoze.profile/trunk/repoze/profile/profiler.py (original)
+++ repoze.profile/trunk/repoze/profile/profiler.py Wed Feb 20 09:12:04 2008
@@ -2,9 +2,20 @@
o Insprired by the paste.debug.profile version, which profiles single requests.
"""
+import os
import profile
+import pstats
+import sys
+import StringIO
import threading
+from paste.request import parse_formvars
+from paste.request import construct_url
+
+import meld3
+
+_HERE = os.path.abspath(os.path.dirname(__file__))
+
DEFAULT_PROFILE_LOG = 'wsgi.prof'
class AccumulatingProfileMiddleware(object):
@@ -13,17 +24,92 @@
global_conf=None,
log_filename=DEFAULT_PROFILE_LOG,
discard_first_request=True,
+ path='/__profile__',
):
self.app = app
self.profiler = profile.Profile()
self.log_filename = log_filename
self.first_request = discard_first_request
self.lock = threading.Lock()
+ self.path = path
+ self.template = os.path.join(_HERE, 'profiler.html')
+ self.meldroot = meld3.parse_xml(self.template)
+
+ def index(self, environ):
+ root = self.meldroot.clone()
+ querydata = parse_formvars(environ)
+ full_dirs = int(querydata.get('full_dirs', 0))
+ sort = querydata.get('sort', 'time')
+ clear = querydata.get('clear', None)
+ limit = int(querydata.get('limit', 100))
+ mode = querydata.get('mode', 'stats')
+ output = StringIO.StringIO()
+ url = construct_url(environ)
+ log_exists = os.path.exists(self.log_filename)
+
+ if clear and log_exists:
+ os.remove(self.log_filename)
+ log_exists = False
+
+ if log_exists:
+ stats = pstats.Stats(self.log_filename)
+ if not full_dirs:
+ stats.strip_dirs()
+ stats.sort_stats(sort)
+
+ try:
+ orig_stdout = sys.stdout
+ sys.stdout = output
+ print_fn = getattr(stats, 'print_%s' % mode)
+ print_fn(limit)
+ sys.stdout.flush()
+ finally:
+ sys.stdout = orig_stdout
+
+ data = output.getvalue()
+
+ form = root.findmeld('form')
+ form.attributes(action=url)
+ if data:
+ description = root.findmeld('description')
+ description.content("""
+ Profiling information is generated using the standard Python
+ profiler. To learn how to interpret the profiler statistics,
+ see the <a
+ href="http://www.python.org/doc/current/lib/module-profile.html">
+ Python profiler documentation</a>.""", structure=True)
+ formelements = root.findmeld('formelements')
+
+ if full_dirs:
+ formelements.fillmeldhtmlform(sort=sort,
+ limit=str(limit),
+ full_dirs=True,
+ mode=mode)
+ else:
+ formelements.fillmeldhtmlform(sort=sort,
+ limit=str(limit),
+ mode=mode)
+
+ profiledata = root.findmeld('profiledata')
+ profiledata.content(data)
+ else:
+ formelements = root.findmeld('formelements')
+ formelements.content('')
+ return root.write_xhtmlstring()
def __call__(self, environ, start_response):
catch_response = []
body = []
+ path_info = environ.get('PATH_INFO')
+
+ if path_info == self.path:
+ # we're being asked to render the profile view
+ body = self.index(environ)
+ start_response('200 OK', [('content-type', 'text/html'),
+ ('content-length', str(len(body)))])
+ return [body]
+
def replace_start_response(status, headers, exc_info=None):
catch_response.extend([status, headers])
start_response(status, headers, exc_info)
@@ -52,11 +138,11 @@
finally:
self.lock.release()
-
def make_profile_middleware(app,
global_conf,
log_filename=DEFAULT_PROFILE_LOG,
discard_first_request=True,
+ path='/__profile__'
):
"""Wrap the application in a component that will profile each
request, appending data from each request to an aggregate
@@ -74,5 +160,6 @@
return AccumulatingProfileMiddleware(
app,
log_filename=log_filename,
- discard_first_request=discard_first_request
+ discard_first_request=discard_first_request,
+ path=path
)
Added: repoze.profile/trunk/repoze/profile/tests.py
==============================================================================
--- (empty file)
+++ repoze.profile/trunk/repoze/profile/tests.py Wed Feb 20 09:12:04 2008
@@ -0,0 +1,118 @@
+import unittest
+
+class TestProfileMiddleware(unittest.TestCase):
+ def _makeOne(self, *arg, **kw):
+ from repoze.profile.profiler import AccumulatingProfileMiddleware
+ return AccumulatingProfileMiddleware(*arg, **kw)
+
+
+ def _makeEnviron(self, kw):
+ environ = {}
+ environ['wsgi.url_scheme'] = 'http'
+ environ['CONTENT_TYPE'] = 'text/html'
+ environ['QUERY_STRING'] = ''
+ environ['SERVER_NAME'] = 'localhost'
+ environ['SERVER_PORT'] = '80'
+ environ['REQUEST_METHOD'] = 'POST'
+ environ.update(kw)
+ return environ
+
+ def test_index_post(self):
+ from StringIO import StringIO
+ fields = [
+ ('full_dirs', '1'),
+ ('sort', 'cumulative'),
+ ('limit', '500'),
+ ('mode', 'callers'),
+ ]
+ content_type, body = encode_multipart_formdata(fields)
+ environ = self._makeEnviron(
+ {'wsgi.input':StringIO(body),
+ 'CONTENT_TYPE':content_type,
+ 'CONTENT_LENGTH':len(body),
+ 'REQUEST_METHOD':'POST',
+ })
+ middleware = self._makeOne(None)
+ html = middleware.index(environ)
+ self.failIf(html.find('There is not yet any profiling data') == -1)
+
+ def test_index_get(self):
+ environ = self._makeEnviron({
+ 'REQUEST_METHOD':'GET',
+ 'wsgi.input':'',
+ })
+ middleware = self._makeOne(None)
+ html = middleware.index(environ)
+ self.failIf(html.find('There is not yet any profiling data') == -1)
+
+ def test_index_clear(self):
+ from StringIO import StringIO
+ fields = [
+ ('full_dirs', '1'),
+ ('sort', 'cumulative'),
+ ('limit', '500'),
+ ('mode', 'callers'),
+ ('clear', 'submit'),
+ ]
+ content_type, body = encode_multipart_formdata(fields)
+ environ = self._makeEnviron(
+ {'wsgi.input':StringIO(body),
+ 'CONTENT_TYPE':content_type,
+ 'CONTENT_LENGTH':len(body),
+ 'REQUEST_METHOD':'POST',
+ })
+
+ middleware = self._makeOne(None)
+ import tempfile
+ f = tempfile.mktemp()
+ open(f, 'w').write('x')
+ middleware.log_filename = f
+ html = middleware.index(environ)
+ self.failIf(html.find('There is not yet any profiling data') == -1)
+ import os
+ self.failIf(os.path.exists(f))
+
+ def test_call_withpath(self):
+ from StringIO import StringIO
+ fields = [
+ ('full_dirs', '1'),
+ ('sort', 'cumulative'),
+ ('limit', '500'),
+ ('mode', 'callers'),
+ ]
+ content_type, body = encode_multipart_formdata(fields)
+ environ = self._makeEnviron(
+ {'wsgi.input':StringIO(body),
+ 'CONTENT_TYPE':content_type,
+ 'CONTENT_LENGTH':len(body),
+ })
+
+ middleware = self._makeOne(None)
+ environ['PATH_INFO'] = middleware.path
+ statuses = []
+ headerses = []
+ def start_response(status, headers):
+ statuses.append(status)
+ headerses.append(headers)
+ iterable = middleware(environ, start_response)
+ html = iterable[0]
+ self.failIf(html.find('There is not yet any profiling data') == -1)
+ self.assertEqual(statuses[0], '200 OK')
+ self.assertEqual(headerses[0][0], ('content-type', 'text/html'))
+ self.assertEqual(headerses[0][1], ('content-length', str(len(html))))
+
+def encode_multipart_formdata(fields):
+ BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
+ CRLF = '\r\n'
+ L = []
+ for (key, value) in fields:
+ L.append('--' + BOUNDARY)
+ L.append('Content-Disposition: form-data; name="%s"' % key)
+ L.append('')
+ L.append(value)
+ L.append('--' + BOUNDARY + '--')
+ L.append('')
+ body = CRLF.join(L)
+ content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
+ return content_type, body
+
Modified: repoze.profile/trunk/setup.py
==============================================================================
--- repoze.profile/trunk/setup.py (original)
+++ repoze.profile/trunk/setup.py Wed Feb 20 09:12:04 2008
@@ -47,9 +47,9 @@
include_package_data=True,
namespace_packages=['repoze'],
zip_safe=False,
- tests_require = [],
- install_requires=[],
- #test_suite="repoze.",
+ tests_require = ['meld3', 'Paste'],
+ install_requires=['meld3', 'Paste'],
+ test_suite="repoze.profile.tests",
entry_points = """\
[paste.filter_app_factory]
profile = repoze.profile.profiler:make_profile_middleware
More information about the Repoze-checkins
mailing list