Skip to content

Tutorial

This tutorial shows how you can use dj-tracker to monitor and improve your queries.

We'll take the example of building a page that shows all books of a library.

The source code is available in the tutorial directory. The README contains instructions to set up the project locally. However, it isn't required and you can just follow along to understand how you might use it in your own project.

Models

We'll work with the following models:

from django.db import models


class Category(models.Model):
    name = models.CharField(max_length=64)


class Author(models.Model):
    first_name = models.CharField(max_length=64)
    last_name = models.CharField(max_length=64)
    date_of_birth = models.DateTimeField()
    date_of_death = models.DateTimeField(null=True, blank=True)
    biography = models.TextField()


class Book(models.Model):
    title = models.CharField(max_length=255)
    summary = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

We also define some factories to build Book instances to work with:

import factory

from .models import Author, Book, Category


class CategoryFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Category

    name = factory.Faker("word")


class AuthorFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Author

    first_name = factory.Faker("first_name")
    last_name = factory.Faker("last_name")
    date_of_birth = factory.Faker("date_of_birth")
    date_of_death = factory.Faker("date_time_this_century")
    biography = factory.Faker("text", max_nb_chars=5000)


class BookFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Book

    title = factory.Faker("sentence")
    summary = factory.Faker("text", max_nb_chars=2500)
    author = factory.SubFactory(AuthorFactory)
    category = factory.SubFactory(CategoryFactory)


create_books = BookFactory.create_batch

Keep in mind how we set a large content for the summary and biography fields.

In a terminal, we create 4000 books to work with:

python manage.py shell -c  "from app.factories import create_books; create_books(4000)"

View - Template

The view that shows all books in the database is defined as follow:

from django.shortcuts import render

from .models import Book


def books_list(request):
    books = Book.objects.all()
    return render(request, "books.html", {"books": books})

and this is the corresponding template:

{% for book in books %}
<h4>{{ book.title }}</h4>
<dl>
  <dt>Author</dt>
  <dd>{{ book.author.first_name }} {{ book.author.last_name }}</dd>

  <dt>Category</dt>
  <dd>{{ book.category.name }}</dd>
</dl>
{% endfor %}

URLs

To see how our view performs, we have additional /time/ and /memory/ endpoints to track timings and memory usage:

from django.urls import path

from .profile import MemoryProfiler, TimeProfiler
from .views import books_list

urlpatterns = [
    path("", books_list),
    path("time/", TimeProfiler(books_list)),
    path("memory/", MemoryProfiler(books_list)),
]

Profiling

We'll use the following methodology to profile the view:

  • Make 10 requests sequentially to the /time/ endpoint
  • Make 10 requests sequentially to the /memory/ endpoint
  • Make 1 request to the books endpoint with dj-tracker running

We run each of these steps in a new process to have consistent results.

Let's now run the first benchmark to see how our view performs:

Time in ms (10 calls) - Min: 3517.68, Max: 4557.71, Avg: 3964.97

Memory - size in KiB (10 calls) - Min: 38986.07, Max: 39572.61, Avg: 39151.99
Memory - peak in KiB (10 calls) - Min: 41946.51, Max: 42529.80, Avg: 42112.31

Our view takes 4s to render and uses around 40Mb in average (in my machine)!

dj-tracker dashboard

Here is how the dj-tracker dashboard looks like at this point:

In the following steps, we'll take a closer look at the informations dj-tracker gives us to see how we can improve our view.