Ever longed for the good old PHP days? No? Good.

Earlier today, i.e. a few minutes ago, I was working on my latest guinea pig, TiThess. It’s an events guide for my city, and the latest project I’m trying everything on. I like having a project I can try new things on, as it helps keep my skills sharp.

As I was testing page load times with the excellent Web Page Test, trying to get them down to the absolute minimum, I was getting an F in time-to-first-byte. This is very odd, because the whole site is supposed to be cached, so I was wondering whether the cache is doing something wrong and slowing page generation down.

To make sure, I needed a simple way to show how long page generation took, like the old “page generated in X seconds” footer that was all the rage with PHP sites way back when. Here’s how I did it:

Displaying the page generation time

As I didn’t want to be cheesy, I wanted the output to be available, but discreet. A header is the best place for this, so all I needed was something to calculate how long the response took to put together. No better way to do this than Django middleware. Here’s the class that does the entire thing (put it in a file called something like stats_middleware.py:

import time


class StatsMiddleware(object):
    def process_request(self, request):
        "Store the start time when the request comes in."
        request.start_time = time.time()

    def process_response(self, request, response):
        "Calculate and output the page generation duration"
        # Get the start time from the request and calculate how long
        # the response took.
        duration = time.time() - request.start_time

        # Add the header.
        response["X-Page-Generation-Duration-ms"] = int(duration * 1000)
        return response

That’s all there’s to it. Just store the time when the request comes in, and retrieve it later. To install the middleware above, just add it to your settings.py:

MIDDLEWARE_CLASSES = (
    'project.stats_middleware.StatsMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    ...
)

Thanks to the magic of Django middleware processing cycle, adding the class above first in our MIDDLEWARE_CLASSES guarantees that it will be the first thing that runs when the request comes in and the last thing that runs when the response goes out. This allows us to measure the full generation time, including any other middleware.

Conversely, if you want to only measure the time it took to run your specific view, and not any middleware, just move the above entry to the end of the MIDDLEWARE_CLASSES list.

Let me know in the comments below or on Twitter if you found this useful. Kisses!