2012-09-01 17:25:58 -04:00
Active Support Instrumentation
==============================
2012-03-05 18:08:15 -05:00
Active Support is a part of core Rails that provides Ruby language extensions, utilities and other things. One of the things it includes is an instrumentation API that can be used inside an application to measure certain actions that occur within Ruby code, such as that inside a Rails application or the framework itself. It is not limited to Rails, however. It can be used independently in other Ruby scripts if it is so desired.
In this guide, you will learn how to use the instrumentation API inside of ActiveSupport to measure events inside of Rails and other Ruby code. We cover:
* What instrumentation can provide
* The hooks inside the Rails framework for instrumentation
* Adding a subscriber to a hook
* Building a custom instrumentation implementation
2012-09-01 17:25:58 -04:00
--------------------------------------------------------------------------------
2012-03-05 18:08:15 -05:00
2012-09-01 17:25:58 -04:00
Introduction to instrumentation
-------------------------------
2012-03-05 18:08:15 -05:00
The instrumentation API provided by ActiveSupport allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in < TODO: link to section detailing each hook point > . With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
2012-09-06 22:26:59 -04:00
For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be **subscribed** to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
2012-03-05 18:08:15 -05:00
You are even able to create your own events inside your application which you can later subscribe to.
2012-09-01 17:25:58 -04:00
Rails framework hooks
---------------------
2012-03-05 18:08:15 -05:00
2012-03-07 10:36:08 -05:00
Within the Ruby on Rails framework, there are a number of hooks provided for common events. These are detailed below.
2012-09-01 17:25:58 -04:00
ActionController
----------------
2012-03-05 18:08:15 -05:00
2012-09-01 17:25:58 -04:00
### write_fragment.action_controller
2012-03-07 10:36:08 -05:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | ---------------- |
| `:key` | The complete key |
2012-03-07 10:36:08 -05:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:key => 'posts/1-dasboard-view'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### read_fragment.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | ---------------- |
| `:key` | The complete key |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:key => 'posts/1-dasboard-view'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### expire_fragment.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | ---------------- |
| `:key` | The complete key |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:key => 'posts/1-dasboard-view'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### exist_fragment?.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | ---------------- |
| `:key` | The complete key |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:key => 'posts/1-dasboard-view'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### write_page.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------- | ----------------- |
| `:path` | The complete path |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:path => '/users/1'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### expire_page.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------- | ----------------- |
| `:path` | The complete path |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:path => '/users/1'
}
2012-09-01 17:08:06 -04:00
```
2012-03-07 10:36:08 -05:00
2012-09-01 17:25:58 -04:00
### start_processing.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------------- | --------------------------------------------------------- |
| `:controller` | The controller name |
| `:action` | The action |
| `:params` | Hash of request parameters without any filtered parameter |
| `:format` | html/js/json/xml etc |
| `:method` | HTTP request verb |
| `:path` | Request path |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:controller => "PostsController",
:action => "new",
:params => { "action" => "new", "controller" => "posts" },
:format => :html,
:method => "GET",
:path => "/posts/new"
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### process_action.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| --------------- | --------------------------------------------------------- |
| `:controller` | The controller name |
| `:action` | The action |
| `:params` | Hash of request parameters without any filtered parameter |
| `:format` | html/js/json/xml etc |
| `:method` | HTTP request verb |
| `:path` | Request path |
| `:view_runtime` | Amount spent in view in ms |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:controller => "PostsController",
:action => "index",
:params => {"action" => "index", "controller" => "posts"},
:format => :html,
:method => "GET",
:path => "/posts",
:status => 200,
:view_runtime => 46.848,
:db_runtime => 0.157
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### send_file.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------- | ------------------------- |
| `:path` | Complete path to the file |
2012-03-15 05:08:16 -04:00
INFO. Additional keys may be added by the caller.
2012-09-01 17:25:58 -04:00
### send_data.action_controller
2012-03-15 05:08:16 -04:00
2012-09-01 21:37:59 -04:00
`ActionController` does not had any specific information to the payload. All options are passed through to the payload.
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### redirect_to.action_controller
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ----------- | ------------------ |
| `:status` | HTTP response code |
| `:location` | URL to redirect to |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
2012-03-15 08:41:28 -04:00
:status => 302,
2012-03-15 05:08:16 -04:00
:location => "http://localhost:3000/posts/new"
}
2012-09-01 17:08:06 -04:00
```
2012-03-07 10:36:08 -05:00
2012-09-01 17:25:58 -04:00
### halted_callback.action_controller
2012-03-07 10:36:08 -05:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| --------- | ----------------------------- |
| `:filter` | Filter that halted the action |
2012-03-07 10:36:08 -05:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:filter => ":halting_filter"
}
2012-09-01 17:08:06 -04:00
```
2012-03-07 10:39:20 -05:00
2012-09-01 17:25:58 -04:00
ActionView
----------
2012-03-07 10:39:20 -05:00
2012-09-01 17:25:58 -04:00
### render_template.action_view
2012-03-07 10:39:20 -05:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------------- | --------------------- |
| `:identifier` | Full path to template |
| `:layout` | Applicable layout |
2012-03-07 10:39:20 -05:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:identifier => "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
:layout => "layouts/application"
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### render_partial.action_view
2012-03-07 10:39:20 -05:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------------- | --------------------- |
| `:identifier` | Full path to template |
2012-03-05 18:08:15 -05:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:identifier => "/Users/adam/projects/notifications/app/views/posts/_form.html.erb",
}
2012-09-01 17:08:06 -04:00
```
2012-03-11 19:18:48 -04:00
2012-09-01 17:25:58 -04:00
ActiveRecord
------------
2012-03-11 19:18:48 -04:00
2012-09-01 17:25:58 -04:00
### sql.active_record
2012-03-11 19:18:48 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------------ | --------------------- |
| `:sql` | SQL statement |
| `:name` | Name of the operation |
| `:object_id` | `self.object_id` |
2012-03-11 19:18:48 -04:00
2012-03-15 05:08:16 -04:00
INFO. The adapters will add their own data as well.
2012-03-11 19:18:48 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:sql => "SELECT \"posts\".* FROM \"posts\" ",
:name => "Post Load",
:connection_id => 70307250813140,
:binds => []
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### identity.active_record
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ---------------- | ----------------------------------------- |
| `:line` | Primary Key of object in the identity map |
| `:name` | Record's class |
| `:connection_id` | `self.object_id` |
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
ActionMailer
------------
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### receive.action_mailer
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------------- | -------------------------------------------- |
| `:mailer` | Name of the mailer class |
| `:message_id` | ID of the message, generated by the Mail gem |
| `:subject` | Subject of the mail |
| `:to` | To address(es) of the mail |
| `:from` | From address of the mail |
| `:bcc` | BCC addresses of the mail |
| `:cc` | CC addresses of the mail |
| `:date` | Date of the mail |
| `:mail` | The encoded form of the mail |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:mailer => "Notification",
:message_id => "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
:subject => "Rails Guides",
:to => ["users@rails.com", "ddh@rails.com"],
:from => ["me@rails.com"],
:date => Sat, 10 Mar 2012 14:18:09 +0100,
:mail=> "..." # ommitted for beverity
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### deliver.action_mailer
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------------- | -------------------------------------------- |
| `:mailer` | Name of the mailer class |
| `:message_id` | ID of the message, generated by the Mail gem |
| `:subject` | Subject of the mail |
| `:to` | To address(es) of the mail |
| `:from` | From address of the mail |
| `:bcc` | BCC addresses of the mail |
| `:cc` | CC addresses of the mail |
| `:date` | Date of the mail |
| `:mail` | The encoded form of the mail |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:08:16 -04:00
{
:mailer => "Notification",
:message_id => "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
:subject => "Rails Guides",
:to => ["users@rails.com", "ddh@rails.com"],
:from => ["me@rails.com"],
:date => Sat, 10 Mar 2012 14:18:09 +0100,
:mail=> "..." # ommitted for beverity
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
ActiveResource
--------------
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### request.active_resource
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| -------------- | -------------------- |
| `:method` | HTTP method |
| `:request_uri` | Complete URI |
| `:result` | HTTP response object |
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
ActiveSupport
-------------
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### cache_read.active_support
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------------------ | ------------------------------------------------- |
| `:key` | Key used in the store |
| `:hit` | If this read is a hit |
| `:super_operation` | :fetch is added when a read is used with `#fetch` |
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### cache_generate.active_support
2012-03-15 05:08:16 -04:00
2012-09-01 21:37:59 -04:00
This event is only used when `#fetch` is called with a block.
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | --------------------- |
| `:key` | Key used in the store |
2012-03-15 05:08:16 -04:00
INFO. Options passed to fetch will be merged with the payload when writing to the store
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
{
2012-03-15 05:08:16 -04:00
:key => 'name-of-complicated-computation'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### cache_fetch_hit.active_support
2012-03-15 05:08:16 -04:00
2012-09-01 21:37:59 -04:00
This event is only used when `#fetch` is called with a block.
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | --------------------- |
| `:key` | Key used in the store |
2012-03-15 05:08:16 -04:00
INFO. Options passed to fetch will be merged with the payload.
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
{
2012-03-15 05:08:16 -04:00
:key => 'name-of-complicated-computation'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### cache_write.active_support
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | --------------------- |
| `:key` | Key used in the store |
2012-03-15 05:08:16 -04:00
INFO. Cache stores my add their own keys
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
{
2012-03-15 05:08:16 -04:00
:key => 'name-of-complicated-computation'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### cache_delete.active_support
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | --------------------- |
| `:key` | Key used in the store |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
{
2012-03-15 05:08:16 -04:00
:key => 'name-of-complicated-computation'
}
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:08:16 -04:00
2012-09-01 17:25:58 -04:00
### cache_exist?.active_support
2012-03-15 05:08:16 -04:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------ | --------------------- |
| `:key` | Key used in the store |
2012-03-15 05:08:16 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
{
2012-03-15 05:08:16 -04:00
:key => 'name-of-complicated-computation'
}
2012-09-01 17:08:06 -04:00
```
2012-03-11 19:18:48 -04:00
2012-09-01 17:25:58 -04:00
Rails
-----
2012-03-05 18:08:15 -05:00
2012-09-01 17:25:58 -04:00
### deprecation.rails
2012-03-05 18:08:15 -05:00
2012-09-02 13:08:06 -04:00
| Key | Value |
| ------------ | ------------------------------- |
| `:message` | The deprecation warning |
| `:callstack` | Where the deprecation came from |
2012-03-05 18:08:15 -05:00
2012-09-01 17:25:58 -04:00
Subscribing to an event
-----------------------
2012-03-05 18:08:15 -05:00
2012-09-01 21:37:59 -04:00
Subscribing to an event is easy. Use `ActiveSupport::Notifications.subscribe` with a block to
2012-03-15 05:27:04 -04:00
listen to any notification.
The block receives the following arguments:
2012-09-03 21:21:24 -04:00
* The name of the event
* Time when it started
* Time when it finished
* An unique ID for this event
* The payload (described in previous sections)
2012-03-15 05:27:04 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
2012-03-15 05:27:04 -04:00
# your own custom stuff
Rails.logger.info "#{name} Received!"
end
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:27:04 -04:00
2012-09-01 21:37:59 -04:00
Defining all those block arguments each time can be tedious. You can easily create an `ActiveSupport::Notifications::Event`
2012-03-15 05:27:04 -04:00
from block args like this:
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
2012-03-15 05:27:04 -04:00
event = ActiveSupport::Notification::Event.new args
event.name # => "process_action.action_controller"
event.duration # => 10 (in milliseconds)
event.payload # => { :extra => :information }
Rails.logger.info "#{event} Received!"
end
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:27:04 -04:00
Most times you only care about the data itself. Here is a shortuct to just get the data.
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
2012-03-15 05:27:04 -04:00
data = args.extract_options!
data # { :extra => :information }
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:27:04 -04:00
You may also subscribe to events matching a regular expresssion. This enables you to subscribe to
2012-09-01 21:37:59 -04:00
multiple events at once. Here's you could subscribe to everything from `ActionController` .
2012-03-15 05:27:04 -04:00
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:27:04 -04:00
ActiveSupport::Notifications.subscribe /action_controller/ do |*args|
# inspect all ActionController events
end
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:27:04 -04:00
2012-09-01 17:25:58 -04:00
Creating custom events
----------------------
2012-03-05 18:08:15 -05:00
2012-09-01 21:37:59 -04:00
Adding your own events is easy as well. `ActiveSupport::Notifications` will take care of
all the heavy lifting for you. Simply call `instrument` with a `name` , `payload` and a block.
The notification will be sent after the block returns. `ActiveSupport` will generate the start and end times
as well as the unique ID. All data passed into the `insturment` call will make it into the payload.
2012-03-15 05:27:04 -04:00
Here's an example:
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 08:41:28 -04:00
ActiveSupport::Notifications.instrument "my.custom.event", :this => :data do
2012-03-15 05:27:04 -04:00
# do your custom stuff here
end
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:27:04 -04:00
Now you can listen to this event with:
2012-09-01 17:08:06 -04:00
```ruby
2012-03-15 05:27:04 -04:00
ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
puts data.inspect # { :this => :data }
end
2012-09-01 17:08:06 -04:00
```
2012-03-15 05:27:04 -04:00
2012-09-01 21:37:59 -04:00
You should follow Rails conventions when defining your own events. The format is: `event.library` .
If you application is sending Tweets, you should create an event named `tweet.twitter` .