Measure your views

The question that brings most people here is which view is slow. The two middlewares answer it without you touching a view.

        flowchart TB
    R([Request]) --> SM

    subgraph stack [MIDDLEWARE]
        direction TB
        SM["StatsdMiddleware<br/><i>opens the scope</i>"]
        YOURS["your middlewares"]
        SMT["StatsdMiddlewareTimer<br/><i>times the view</i>"]
        SM --> YOURS --> SMT
    end

    SMT --> V["view<br/><i>request.statsd</i>"]
    V --> DB[("execute_wrapper<br/>sql.&lt;alias&gt;")]
    V --> T["render_to_string<br/>render_django"]

    SM -. "scope on asgiref Local" .-> SCOPE(["timings, counters,<br/>view name"])
    SCOPE -. "flushed on response" .-> OUT[/"statsd, UDP 8125"/]

    classDef ours fill:#3b6ea5,stroke:#2a4f78,color:#fff
    classDef theirs fill:#eef2f6,stroke:#b6c2ce,color:#1b1f23
    class SM,SMT,SCOPE ours
    class YOURS,V,DB,T,OUT theirs

    

StatsdMiddleware sits at the top of the stack and opens a scope on the request. StatsdMiddlewareTimer sits at the bottom and times the view itself. Everything you install between them is timed by the pair. Put them the wrong way round and the timer stops a clock nobody started. The first request tells you so:

AssertionError: Unable to stop tracking process_response, never
started tracking it

You find out on the first request in development, so this is a mistake you make once.

What one request submits

# GET /dashboard/

myproject.view.get.myproject.views.dashboard.hit
myproject.view.get.myproject.views.dashboard.process_request
myproject.view.get.myproject.views.dashboard.process_response
myproject.view.get.myproject.views.dashboard.process_view
myproject.view.get.myproject.views.dashboard.total
myproject.view.http_codes.2xx
myproject.view.http_codes.hit
myproject.view.site.hit

process_request covers the middlewares above the view, process_view the resolution and the view call, and process_response the way back out. They don’t add up to total, and the gap is real: it’s the part of the request outside the section the middleware pair wraps.

Errors

A view that raises reports under the same name, with two differences:

# GET /broken/, which raises

myproject.view.get.myproject.views.broken.hit
myproject.view.get.myproject.views.broken.process_exception
myproject.view.get.myproject.views.broken.process_request
myproject.view.get.myproject.views.broken.process_response
myproject.view.get.myproject.views.broken.process_view
myproject.view.get.myproject.views.broken.total
myproject.view.http_codes.5xx
myproject.view.http_codes.hit
myproject.view.site.hit

process_exception appears, and the status counter lands on http_codes.5xx. The view is still timed, so a view that fails slowly shows up as a slow view and not as a gap.

Skipping views

Health checks and the admin generate traffic nobody charts. STATSD_VIEWS_TO_SKIP holds regular expressions matched against the view name, and the admin is skipped out of the box.

STATSD_VIEWS_TO_SKIP = [
    r'django.contrib.admin',
    r'myproject.views.health_check',
]

Ajax requests

STATSD_TAGS_LIKE files an ajax view under a tagged name as well as its plain one, so you can chart the two apart without losing the combined figure. The separators it understands are _is_ and =.