Problem/Motivation
Providing server timing would help developers estimate site performance.
The corresponding W3C specification is currently in "Working Draft" status and its already supported by Google Chrome and Firefox.
https://www.w3.org/TR/server-timing/
Symfony adopted it in https://github.com/symfony/symfony/pull/27985
Proposed resolution
Add Server-Timing header with relevant timing information.
Remaining tasks
Decide how enable the Server-Timing.
Proposals
- Add it to develoment.services.yml (#2)
- Enable it be default, for example by adding it to core.services.yml (#7)
- Create a new module for it (#24)
- Toggle it via service parameter (#27)
- Toggle it via configuration option (#38)
| Comment | File | Size | Author |
|---|---|---|---|
| #58 | 3104566-58.patch | 7.28 KB | rajandro |
| #2 | server-timing-3104566-2.patch | 2.01 KB | chi |
Comments
Comment #2
chi commentedThe patch adds a middleware that is supposed to be enabled in develoment.services.yml. Currently it only logs total execution time. I am not sure if it's feasible to provide more detailed information, like bootstrap time, DB queries time, rendering time and so on.

Comment #3
chi commentedNote that Server-Timing permits metrics without duration. We can supply some additional information related to site performance, like cache hit/miss.
Comment #5
chi commentedFixed tests.
Comment #6
wim leersComment #7
wim leersI love this!
We could add more detail, which is what #2851733: Expose collected metrics via Server-Timing response header suggested. But … that also means much more bikeshedding. This is simple. We can have follow-ups to add more detail. But that detail could potentially be used in an adversarial way (e.g. exposing DB query time would enable malicious users to figure out the most expensive route, and then hit that route in a DDoS attack).
That risk doesn't exist in this patch.
I know you're only adding it to
development.services.ymlso the aforementioned risks don't really apply, but wouldn't it be great to expose at least the "total" timing always, also on production sites? That's a key goal of the Server Timing spec: https://www.w3.org/TR/server-timing/#server-timing-for-automated-analyticsÜbernit: The decorated kernel.
Nit: this is not setting the
$replaceparameter. Which means its default is used:$replace = TRUE. That means this call is overwriting any pre-existingServer-Timingresponse header. We should instead be merging these.(And ideally we'd have test coverage proving that this appends to any existing
Server-Timingresponse header, instead of overwriting it.)P.S.: I touched on something similar last week: https://git.drupalcode.org/project/http2_server_push/commit/fc0cc00.
Comment #8
chi commentedAddressed issues #7.
Comment #9
chi commentedAdded missing test group.
Comment #10
chi commentedMoved service definition to core.services.yml to expose total time on production as it was suggested in #7.
Also changed the description from "Total" to "Page execution time" as the latter seems more meaningful.
I did not change the timing name because it appears that "total" has a special meaning for Google Chrome though I could find any references about this in the specification.
From my observation if server timing is named "total" Google Chrome applies the following formatting rules to it.
Comment #12
chi commentedFixed entity resource tests.
Comment #14
wim leersExciting progress, @Chi!
FANCY!
$this→prophesize(HttpKernelInterface::class)→reveal()is nowadays the preferred way of doing this.$this→assertSame()is nowadays preferred.Comment #15
chi commentedAddressed #14 and fixed JSON API tests.
Comment #16
chi commentedDoes this need a change record?
Comment #18
wim leersYes, I think this should have a change record, to make as many people aware as possible :)
I think it'd be great to add test coverage for the edge case I described in #7.2 — now that there is a unit test, it's easy to test that 🥳
Comment #19
chi commentedComment #20
chi commentedThe change record is ready for review.
https://www.drupal.org/node/3105166
Comment #21
chi commentedThe support of Server Timing has been added to Firefox 71 though apparently it only works through HTTPS.
https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/71
Comment #22
wim leers🤔 Nit: Can change to just
@covers ::handleI think?🤓 Nit: extraneous empty line.
Those are übertrivial nits though, and not worth holding this patch up for.
Comment #23
chi commentedFixed #22.
Comment #24
catchI understand not making this development only since it's viable to want it enabled on production, but do we really want to enable it on production for every site? It's a syscall to get the microtime for the request, and even if you want this on production, you'd have to make a conscious effort to use it I think? Wondering if we want a tiny module to do this instead.
Comment #25
wim leersI'm not sure what you mean by "conscious effort" here. It's available to any end user (which includes developers of the site) as well as analytics (Google Analytics or otherwise).
Comment #26
longwaveIt does bloat every single response with an extra header, which some sites might not want? There is also perhaps a better chance of something like a crypto-related timing attack if you can more accurately measure how long a request took?
Comment #27
jhedstromI think a small module makes sense, or if not, perhaps the behavior could be toggled on and off in a similar manner to the way cache tag headers (
X-Drupal-Cache-Tags)?Comment #28
chi commentedHaving a separate module for such a small task does not seem reasonable unless we have more features to include into it.
Can we just add a configuration option for this?
That could have a checkbox on Administration -> Configuration -> Development -> Performance form.
Comment #29
chi commentedI summarized possible implementations in the issue summary.
Comment #30
catch@WimLeers you have to make a conscious effort to use analytics no?
Comment #31
andypostIt should not fire on subrequests and that needs test coverage
Comment #32
wim leers#26:
RE: extra header: it compresses to nearly nothing and we've got far worse bloat in our responses, such as
X-Generatorwhich has definitely no value at all. You're right in absolute terms of course! :) But in relative terms, I don't see significant cost here.RE: crypto-related timing attacks relate to response size and very precise differences in timing. Drupal response times do not depend on crypto logic, but on data retrieval and assembling a response. If Drupal were extremely tightly optimized and had extremely low-latency I/O for its data retrieval, then I would share this concern. Right now, this is a concern I would love to have 🤓😄
#30: Good point. But if analytics have already been set up, then this enables them to provide richer insights without any additional conscious effort.
Comment #33
daffie commented+1 for me.
Comment #34
borisson_#28: I agree, it doesn't seem needed to add a new module for this. I also don't think we need to add a configuration option for this in the UI. That could alway be added later.
#31: That does seem like something we can add, I'm not sure if we need to add this in this patch, I think we can add this in the future as a followup to this?
I reviewed the patch and it looks great, I'm sitting next to @Wim Leers at the global sprint weekend and he explained why we should implement this. +1.
Comment #35
andypostbtw Probably it needs review from project manager pov
Comment #36
alexpottThe request stack is empty but we have the request - so we can do
$request->server->get('REQUEST_TIME_FLOAT')instead of$_SERVER['REQUEST_TIME_FLOAT']It'd be great to get a +1 from @catch
FWIW I think this patch and approach is okay. If you don't want this information exposed you can remove the service but it seems helpful to expose by default and it is not exposing anything that represents a security risk.
Comment #37
chi commentedAddressed #36 and moved ClockMock close to
$middleware->handle($request);.Comment #38
andypost@Chi maybe this middleware should check that only for master request?
Comment #39
andypostAdded https://github.com/symfony/symfony/pull/27985/files to summary - SF adopted this header too
I suggest to use kinda "Request execution time" because not all requests produce a Page
Comment #40
chi commentedWhat about "Total execution time"?
Comment #41
chi commentedAddressed #38 and #39.
Comment #42
johnwebdev commentedCode looks good, and this is nice.
To address the concerns from previous comments, perhaps we could add a link to the change record on how to remove this.
Comment #44
neslee canil pintoTest Failed to #41, Moving to Needs Work
Comment #45
andypostI bet it proper branch
Comment #46
neslee canil pintoComment #47
chi commentedI think that was a random test failure, back to RTBC as #41 passing tests now.
Comment #48
alexpottI think we should only be adding this in 9.1.x now as both 8.9.x and 9.0.x are in beta.
#46 is failing because \Symfony\Component\HttpFoundation\HeaderBag::get() with 3 arguments is deprecated in Symfony 4.
So that means
Should be:
self::assertSame($expected_headers, $response->headers->all('Server-Timing'));Comment #49
alexpottThere's no test coverage of the condition
if ($type == self::MASTER_REQUEST) {as far as I can see.Comment #50
neslee canil pintoComment #51
johnwebdev commentedComment #53
johnwebdev commentedComment #54
andypostGreat tests! Back to rtbc
Comment #55
xjmNice work on this; it's an interesting proposal.
I think we need an explicit signoff from the framework managers here: @alexpott indicated he was OK with it, but @catch had some questions and I think we still need some final consensus/signoff.
Comment #56
catchIf we're going to add this without an additional module, I think we need profiling to see if the call to
microtime()makes a measurable difference with the internal page cache (this is also a reason not to use the config system since that'd need to be checked in the internal page cache too, unless we introduce a dependency between the configuration value and the container).microtime()results in a syscall that we otherwise usually avoid via the various request time superglobals.Comment #57
chi commentedComment #58
rajandro commentedRerolled the last patch (3104566-52.patch) as per the issue tag. Please review it.
Comment #59
rajandro commentedComment #60
andypostComment #62
moshe weitzman commentedI think this is a worthwhile change, even considering the new microtime() call on page cached pages. If thats going to be a blocker, could we remove this feature when page cache is successful for a given page or even when its enabled on the site? Just looking for a compromise.
Comment #63
chi commentedI think the compromise could be reversion to the original approach. which is having server timing only in development environment. In this case performance of
microtime()would not be so important. Actually, I think on development environment it would be hard to notice the difference because of null backends for cache bins.Other points are:
Comment #64
andypostother side of "disabled by default" is that just few people will use it as new setting for sure will be skipped
Comment #65
longwaveProposal 3, making a separate module for it, allows it to be discovered easily while solving the other problems?
Comment #66
andypost@longwave do you mean experimental telemetry module and initiative?
Comment #67
longwaveWhy not just a small core module? page_cache is one service and a cache bin, automated_cron is one service and some config. server_timing module could just add this one service?
Comment #68
chi commentedRe #64. It'll be enable by default but only for development environment.
Comment #70
moshe weitzman commentedThat sounds good to me.
Comment #74
andypostFor development time there's better tools now like https://github.com/brettmc/opentelemetry-php-contrib/blob/main/src/Instr...