Bradley Kirton's Blog

Published on Nov. 19, 2025

Go home

Simple Server Timing in Django

Server Timing

This middleware attaches a timings object to a request which can be used to add server timings to the response headers.

import collections
import contextlib
import time

from django.http import HttpRequest, HttpResponse
from django.utils.deprecation import MiddlewareMixin


class Timings(collections.UserList[str]):
    @contextlib.contextmanager
    def timeit(self, name: str) -> t.Iterator[None]:
        start = time.monotonic_ns()
        yield
        dur_ns = time.monotonic_ns() - start
        dur = dur_ns / 1_000_000
        self.data.append(f"{name};dur={dur}")

    def serialize(self) -> str:
        return ",".join(self.data)


class ServerTimingMiddleware(MiddlewareMixin):
    """Adds server timing headers to the response."""

    def process_request(self, request: HttpRequest) -> None:
        request._timings_total_start = time.monotonic_ns()  # type:ignore
        request.timings = Timings()  # type:ignore

    def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
        if not hasattr(request, "timings"):
            return response

        timings = request.timings  # type:ignore
        dur_ns = time.monotonic_ns() - request._timings_total_start  # type: ignore
        dur = dur_ns / 1_000_000
        timings.data.append(f"total;dur={dur}")
        response["Server-Timing"] = timings.serialize()
        return response

Usage

def view(request: HttpRequest) -> HttpResponse:
    with request.timings.timeit(name="Expensive query"):
        ...