diff --git a/server/djrandom/frontend/frontend.py b/server/djrandom/frontend/frontend.py index 8608c80468e3870d8ef647bcd2397b350fee6787..8129be6152caf1988b177754f9188d248a802f54 100644 --- a/server/djrandom/frontend/frontend.py +++ b/server/djrandom/frontend/frontend.py @@ -39,6 +39,13 @@ def run_frontend(opts): # Start the WSGI profiling middleware, if requested. if opts.profile: + from djrandom.frontend.latency import LatencyProfilerMiddleware + app.wsgi_app = LatencyProfilerMiddleware( + app.wsgi_app, + ['/json/album', '/json/song', '/album_image', + '/json/playlist/get', '/json/playlist/by_title', + '/json/playlist/list', '/user/activate']) + from repoze.profile.profiler import AccumulatingProfileMiddleware app.wsgi_app = AccumulatingProfileMiddleware( app.wsgi_app, diff --git a/server/djrandom/frontend/latency.py b/server/djrandom/frontend/latency.py new file mode 100644 index 0000000000000000000000000000000000000000..b8f3a7ea6a6302647b46023695c512603d1b183f --- /dev/null +++ b/server/djrandom/frontend/latency.py @@ -0,0 +1,81 @@ +import collections +import time +import pygooglechart + + +LATENCY_BUCKETS = [ + 1, 2, 5, + 10, 20, 50, + 100, 200, 500, + ] + +N_BUCKETS = len(LATENCY_BUCKETS) + 1 + + +def _build_buckets(): + return [0] * N_BUCKETS + +def _percent(counts): + tot = float(sum(counts)) + return [(x, 100 * x / tot) for x in counts] + + +class LatencyProfilerMiddleware(object): + + def __init__(self, app, urls=[]): + self._app = app + self._urls = sorted(urls, key=lambda x: len(x), reverse=True) + self._buckets = collections.defaultdict(_build_buckets) + + def __call__(self, environ, start_response): + start = time.time() + url = environ['PATH_INFO'] + if url.endswith('/__latency__'): + return self.handler(environ, start_response) + + try: + return self._app(environ, start_response) + finally: + elapsed_ms = 1000 * (time.time() - start) + for url_prefix in self._urls: + if url.startswith(url_prefix): + url = url_prefix + break + for bkt, threshold in enumerate(LATENCY_BUCKETS): + if elapsed_ms < threshold: + break + else: + bkt = len(LATENCY_BUCKETS) + self._buckets[url][bkt] += 1 + + def handler(self, environ, start_response): + headers = [('Content-type', 'text/html')] + start_response('200 OK', headers) + + result = [ + '<!doctype html>\n' + '<html><head><title>Latency Breakdown</title>\n' + '<style type="text/css">\n' + 'body { background: white; font-family: sans-serif; }\n' + '</style></head><body>\n' + '<h3>Latency report by URL</h3>\n' + '<table><thead><tr>' + '<th rowspan="2">URL</th>', + '<th colspan="%d">Latencies (ms)</th>' % N_BUCKETS, + '</tr><tr>' + ] + + for ms in enumerate(LATENCY_BUCKETS): + result.append('<th><%d</th>' % ms) + result.append('<th>>%d</th>' % ms) + result.append('</tr></thead><tbody>') + + for url in sorted(self._buckets.keys()): + result.append('<tr><td class="url">%s</td>' % url) + for count, pct in _percent(self._buckets[url]): + result.append('<td>%d (%.3g%%)</td>' % (count, pct)) + result.append('</tr>') + + result.append('</tbody></table>') + result.append('</body></html>\n') + return result