Skip to content

Add Server Timing middleware#36289

Merged
guilleiguaran merged 7 commits intorails:mainfrom
sogamoso:server-timing
Sep 20, 2021
Merged

Add Server Timing middleware#36289
guilleiguaran merged 7 commits intorails:mainfrom
sogamoso:server-timing

Conversation

@sogamoso
Copy link
Contributor

@sogamoso sogamoso commented May 15, 2019

Summary

What is server timing?

It's a specification that defines how the server can communicate the
user agent performance metrics about the request it is responding to.

Here's the official specification: https://www.w3.org/TR/server-timing/

How does it work?

This introduces a new ServerTiming middleware only on development by
default, this is done using the config.server_timing setting.

It works by subscribing to all ActiveSupport::Notifications and adding
their duration to the Server-Timing header.

Why is this useful?

It makes looking at performance metrics in development much easier,
especially when using Chrome dev tools which include the metrics in the
Network Inspector. Here's an example:

Paired on this with @guilleiguaran

@guilleiguaran guilleiguaran self-assigned this May 15, 2019
@guilleiguaran guilleiguaran added this to the 6.1.0 milestone May 15, 2019
@guilleiguaran
Copy link
Member

FYI, This was originally planned for GSoC but it wasn't completed so I asked to @sebasoga to work on this.

https://github.com/railsgsoc/ideas/wiki/2018-Ideas#server-timing-middleware

/cc @gsamokovarov

@jeremy
Copy link
Member

jeremy commented May 16, 2019

See also #36220

@rafaelfranca
Copy link
Member

I think we should be merging efforts on #36220 given it is an older PR and the contributor is still active.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we really don't care about what we are doing here we can just use true instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@sogamoso
Copy link
Contributor Author

sogamoso commented May 16, 2019

@guilleiguaran, @jeremy, @rafaelfranca: happy to merge the efforts. I think we both took a different approach so a little bit of direction of how you think the API should look like might help us know what the best way to merge efforts is.

Maybe making the events and opt-in the app config could help reduce potential noise:

config.server_timing = ["process_action.action_controller"]

Would be great if ActiveSupport::Notifications::Event also had a #description that we could use and include in the header. I'd be happy to work on that as well, maybe on a separate branch.

/cc @itsWill

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be events_collection.sum(&:duration) instead of duration_sum(events_collection).

Copy link
Contributor Author

@sogamoso sogamoso Jan 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice suggestion, done in 45c9631.

@igorkasyanchuk
Copy link
Contributor

It looks like a cool feature. Maybe try to backport it to the older version of Rails?

What is server timing?
It's an specification that defines how the server can communicate the
user agent performance metrics about the request it is responding to.

Here's the oficinal specification:
https://www.w3.org/TR/server-timing/#dfn-server-timing-header-field

How does it work?
This introduces a new `ServerTiming` middleware only on `development` by
default, this is done using the `config.server_timing` setting.

It works by subscribing to all `ActiveSupport::Notifications` and adding
their duration to the `Server-Timing` header.

Why is this useful?
It makes looking at perfomance metrics in development much easier,
especially when using Chrime devtools which includes the metrics in the
Network Inspector. Here's an example:

![](https://d3vv6lp55qjaqc.cloudfront.net/items/371h2y3B3a0U470j040u/Image%202019-05-15%20at%205.40.37%20PM.png?)

Paired on this with @guilleiguaran
We only want to be subscribed for the life span of the middleware
execution.
We don't really care about what's going on in the block as long as it
gets executed.
@sogamoso
Copy link
Contributor Author

sogamoso commented Jan 8, 2020

@igorkasyanchuk I'd be glad to give that a try once we settle on an API for this.

Got [this message](https://github.com/rails/rails/commit/45c9631614437b32af264c3fea3dad0333222456/checks?check_suite_id=390902272):
```
Offenses:

actionpack/test/dispatch/server_timing_test.rb:41:3: C: Layout/EmptyLinesAroundAccessModifier: Remove a blank line after private.
  private
  ^^^^^^^

2826 files inspected, 1 offense detected
```
end

status, headers, body = @app.call(env)
ActiveSupport::Notifications.unsubscribe(subscriber)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we unsubscribe in an ensure block, to ensure we don't leak subscriptions if the @app crashes? 🤔

Copy link
Contributor Author

@sogamoso sogamoso Jan 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gsamokovarov that makes sense. Are you thinking about something along these lines?

status, headers, body = begin
  @app.call(env)
ensure
  ActiveSupport::Notifications.unsubscribe(subscriber)
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, something like this should work.

Copy link
Contributor Author

@sogamoso sogamoso Jan 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 6d9ce6e.

config.consider_all_requests_local = true

# Enable server timing
config.server_timing = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about to make this default for development (also default for framework defaults "6.1" )? That will make it easier to use (it will kick-in on its own once "6.1" defaults are loaded) for upgraded apps as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simi I think the most sensible is to make this the default on development only. I agree this would be a nice default for apps upgrading to 6.1+. Let's see what people in the core and committers team think about it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea is to make it default in development only, but without need of explicit configuration entry.

Avoid instantiating an empty array and inserting items into it by using
`map`.
@sogamoso sogamoso force-pushed the server-timing branch 2 times, most recently from f8c6119 to f888f68 Compare January 9, 2020 23:15
We want to make sure that if the application during crashes the Server
Timing middleware unsubscribes before continuing.
@rails-bot
Copy link

rails-bot bot commented Apr 9, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Apr 9, 2020
@ahorek
Copy link

ahorek commented Apr 9, 2020

I would like to see this feature in a future version of Rails, what else needs to be done?

@rails-bot rails-bot bot removed the stale label Apr 9, 2020
@jeremy jeremy modified the milestones: 6.1.0, 6.x Nov 2, 2020
@n-rodriguez
Copy link
Contributor

Hi there! Any news?

@sogamoso
Copy link
Contributor Author

@jeremy, @guilleiguaran is there anything I can do to move forward with this?

Base automatically changed from master to main January 14, 2021 17:01
@georgeclaghorn georgeclaghorn modified the milestones: 6.x, 7.0 Mar 27, 2021
@p8
Copy link
Member

p8 commented Jul 12, 2021

@SebastianS Could you resolve the merge conflicts and add a changelog entry?

@sogamoso
Copy link
Contributor Author

@p8 will do.

@guilleiguaran guilleiguaran merged commit 0547b16 into rails:main Sep 20, 2021
@guilleiguaran
Copy link
Member

@SebastianS sorry for the super-late reply, I've merged this and added the CHANGELOG entry in 54707cb

I think we should explain the new configuration variable added to the Configuring Guide: https://guides.rubyonrails.org/configuring.html

dhh pushed a commit that referenced this pull request Sep 20, 2021
* main:
  [ci skip] Add Bootstrap and Bulma to the CSS processors' list (#43254)
  Add missing configuration to middleware test
  Add ActionDispatch::ServerTiming to the list of middlewares in test
  Add CHANGELOG entry for ServerTiming middleware [ci skip]
  Add Server Timing middleware (#36289)
  Don't overwrite default opts in rich_text_area_tag (#43156)
  Only call `wrap_parameters` if it's defined (#43259)
  Suggest a CSP that's compatible with Turbo + import map (#43227)
  Fix app generator tests
  Fix docs spacing
  Fix typo: integer numbers (not integral)
  Add `beginning_of_week` option to `weekday_options_for_select`
  Pluralize the heading on the index page generated with scaffold
  Move the parameter wrapper code to the ActionController::Railtie class
  Add explicit rendering to DiskController#update
  Fix update & destroy queries when default_scope is nillable with all_queries: true
@sogamoso sogamoso deleted the server-timing branch June 5, 2022 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.