Repository: jazzband/django-debug-toolbar

Issue:
Server-Timing header formatted incorrectly?
The `Server-Timing` response header which was added in #928 exposes a lot of valuable information, but it looks like it's not formatted according to [the latest version of the W3C spec](https://www.w3.org/TR/server-timing/).

A demonstration of the problem can be seen in Chrome's dev tools. `Server-Timings` should be parsed and displayed here, but it's not quite working. Here's the header which is being sent (with added whitespace for readability):

```
Server-Timing: TimerPanel_utime=64.00000000000006; "User CPU time",
    TimerPanel_stime=16.000000000000014; "System CPU time",
    TimerPanel_total=80.00000000000007; "Total CPU time",
    TimerPanel_total_time=80.66487312316895; "Elapsed time",
    SQLPanel_sql_time=1.6126632690429688; "SQL 1 queries",
    CachePanel_total_time=0; "Cache 0 Calls"
```

…and here's how Chrome shows this:

![Screenshot 2019-11-15 at 11 29 13](https://user-images.githubusercontent.com/76330/68940629-9f2be200-079b-11ea-9fcd-094d290a668a.png)

I think the header should be something like this:

```
Server-Timing: TimerPanel_utime;dur=64.00000000000006;desc="User CPU time",
    TimerPanel_stime;dur=16.000000000000014;desc="System CPU time",
    TimerPanel_total;dur=80.00000000000007;desc="Total CPU time",
    TimerPanel_total_time;dur=80.66487312316895;desc="Elapsed time",
    SQLPanel_sql_time;dur=1.6126632690429688;desc="SQL 1 queries",
    CachePanel_total_time;dur=0;desc="Cache 0 Calls"
```


Discussion:
Looks like the Django Debug Toolbar header complies with [an outdated version of the spec](https://www.w3.org/TR/2016/NOTE-server-timing-20160523/).

Resolving patch:
diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py
--- a/debug_toolbar/middleware.py
+++ b/debug_toolbar/middleware.py
@@ -112,9 +112,9 @@ def generate_server_timing_header(response, panels):
                 continue
 
             for key, record in stats.items():
-                # example: `SQLPanel_sql_time=0; "SQL 0 queries"`
+                # example: `SQLPanel_sql_time;dur=0;desc="SQL 0 queries"`
                 data.append(
-                    '{}_{}={}; "{}"'.format(
+                    '{}_{};dur={};desc="{}"'.format(
                         panel.panel_id, key, record.get("value"), record.get("title")
                     )
                 )


Test patch:
diff --git a/tests/test_integration.py b/tests/test_integration.py
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -1,4 +1,5 @@
 import os
+import re
 import unittest
 
 import html5lib
@@ -92,6 +93,7 @@ class DebugToolbarIntegrationTestCase(TestCase):
     def test_middleware(self):
         response = self.client.get("/execute_sql/")
         self.assertEqual(response.status_code, 200)
+        self.assertContains(response, "djDebug")
 
     @override_settings(DEFAULT_CHARSET="iso-8859-1")
     def test_non_utf8_charset(self):
@@ -245,6 +247,20 @@ def test_incercept_redirects(self):
         # Link to LOCATION header.
         self.assertIn(b'href="/regular/redirect/"', response.content)
 
+    def test_server_timing_headers(self):
+        response = self.client.get("/execute_sql/")
+        server_timing = response["Server-Timing"]
+        expected_partials = [
+            r'TimerPanel_utime;dur=(\d)*(\.(\d)*)?;desc="User CPU time", ',
+            r'TimerPanel_stime;dur=(\d)*(\.(\d)*)?;desc="System CPU time", ',
+            r'TimerPanel_total;dur=(\d)*(\.(\d)*)?;desc="Total CPU time", ',
+            r'TimerPanel_total_time;dur=(\d)*(\.(\d)*)?;desc="Elapsed time", ',
+            r'SQLPanel_sql_time;dur=(\d)*(\.(\d)*)?;desc="SQL 1 queries", ',
+            r'CachePanel_total_time;dur=0;desc="Cache 0 Calls"',
+        ]
+        for expected in expected_partials:
+            self.assertTrue(re.compile(expected).search(server_timing))
+
 
 @unittest.skipIf(webdriver is None, "selenium isn't installed")
 @unittest.skipUnless(
diff --git a/tests/views.py b/tests/views.py
--- a/tests/views.py
+++ b/tests/views.py
@@ -1,5 +1,5 @@
 from django.contrib.auth.models import User
-from django.http import HttpResponse, HttpResponseRedirect
+from django.http import HttpResponseRedirect
 from django.shortcuts import render
 from django.template.response import TemplateResponse
 from django.views.decorators.cache import cache_page
@@ -7,7 +7,7 @@
 
 def execute_sql(request):
     list(User.objects.all())
-    return HttpResponse()
+    return render(request, "base.html")
 
 
 def regular_view(request, title):
@@ -25,12 +25,12 @@ def new_user(request, username="joe"):
 
 def resolving_view(request, arg1, arg2):
     # see test_url_resolving in tests.py
-    return HttpResponse()
+    return render(request, "base.html")
 
 
 @cache_page(60)
 def cached_view(request):
-    return HttpResponse()
+    return render(request, "base.html")
 
 
 def regular_jinjia_view(request, title):