Active Support の Instrumentation 機能

Active SupportはRailsのコア機能のひとつであり、Ruby言語の拡張、ユーティリティなどを提供するものです。Active Supportに含まれているInstrumentation APIは、Rubyコードで発生する特定の動作の計測に利用できます。Railsアプリケーション内部やフレームワーク自身も計測できますが、必要であればRails以外のRubyスクリプトなども測定できます。

本ガイドでは、RailsなどのRubyコード内のイベント計測に使う、Active Support内のInstrumentation APIについて解説します。

このガイドの内容:

1 Instrumentationについて

Active Supportが提供するInstrumentation APIを使ってフックを開発すると、他の開発者がそこにフックできるようになります。フックの多くは、Railsフレームワーク向けです。このAPIをアプリケーションで実装すると、アプリケーション(またはRubyコード片)内部でイベントが発生したときに通知を受け取れるよう他の開発者が設定できます。

たとえばActive Recordには、データベースへのSQLクエリが発行されるたびに呼び出されるフックが用意されていますこのフックをサブスクライブ(購読)すると、特定のアクションでのクエリ実行数を追跡できます。他に、コントローラのアクション実行中に呼び出されるフックもあります。このフックは、たとえば特定のアクション実行に要する時間の測定に利用できます。

もちろん、アプリケーション内に独自のイベントを作成し、後で自分でサブスクライブして測定することもできます。

2 Railsフレームワーク用フック

Ruby on Railsでは、フレームワーク内の主なイベント向けのフックが多数提供されています詳しくは次をご覧ください。

3 Action Controller

3.1 write_fragment.action_controller

キー
:key 完全なキー
{
  key: 'posts/1-dashboard-view'
}

3.2 read_fragment.action_controller

キー
:key 完全なキー
{
  key: 'posts/1-dashboard-view'
}

3.3 expire_fragment.action_controller

Key Value
:key 完全なキー
{
  key: 'posts/1-dashboard-view'
}

3.4 exist_fragment?.action_controller

キー
:key 完全なキー
{
  key: 'posts/1-dashboard-view'
}

3.5 write_page.action_controller

キー
:path 完全なパス
{
  path: '/users/1'
}

3.6 expire_page.action_controller

キー
:path 完全なパス
{
  path: '/users/1'
}

3.7 start_processing.action_controller

キー
:controller コントローラ名
:action アクション
:params リクエストパラメータのハッシュ(フィルタされたパラメータは含まない)
:headers リクエスト ヘッダー
:format html/js/json/xml など
:method HTTP リクエストメソッド(verb)
:path リクエスト パス
{
  controller: "PostsController",
  action: "new",
  params: { "action" => "new", "controller" => "posts" },
  headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
  format: :html,
  method: "GET",
  path: "/posts/new"
}

3.8 process_action.action_controller

キー
:controller コントローラ名
:action アクション
:params リクエストパラメータのハッシュ(フィルタされたパラメータは含まない)
:headers リクエスト ヘッダー
:format html/js/json/xml など
:method HTTP リクエストメソッド(verb)
:path リクエスト パス
:status HTTP ステータスコード
:view_runtime ビューでかかった合計時間(ms)
:db_runtime データベースへのクエリ実行にかかった時間(ms)
{
  controller: "PostsController",
  action: "index",
  params: {"action" => "index", "controller" => "posts"},
  headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
  format: :html,
  method: "GET",
  path: "/posts",
  status: 200,
  view_runtime: 46.848,
  db_runtime: 0.157
}

3.9 send_file.action_controller

キー
:path ファイルへの完全なパス

呼び出し側でキーが追加される可能性があります。

3.10 send_data.action_controller

ActionController自身は、ペイロードに情報を持ちません。オプションは、すべてペイロード経由で渡されます。

3.11 redirect_to.action_controller

キー
:status HTTP レスポンス コード
:location リダイレクト先URL
{
  status: 302,
  location: "http://localhost:3000/posts/new"
}

3.12 halted_callback.action_controller

キー
:filter アクションを停止させたフィルタ
{
  filter: ":halting_filter"
}

4 Action View

4.1 render_template.action_view

キー
:identifier テンプレートへの完全なパス
:layout 該当のレイアウト
{
  identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
  layout: "layouts/application"
}

4.2 render_partial.action_view

キー
:identifier テンプレートへの完全なパス
{
  identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb"
}

5 Active Record

5.1 sql.active_record

キー
:sql SQL文
:name 操作の名前
:connection_id self.object_id
:binds パラメータの割り当て(バインド)

アダプタも独自のデータを追加します。

{
  sql: "SELECT \"posts\".* FROM \"posts\" ",
  name: "Post Load",
  connection_id: 70307250813140,
  binds: []
}

5.2 instantiation.active_record

Key Value
:record_count レコードのインスタンス数
:class_name レコードのクラス
{
  record_count: 1,
  class_name: "User"
}

6 Action Mailer

6.1 receive.action_mailer

キー
:mailer メイラークラス名
:message_id Mail gemが生成したメッセージID
:subject メールの件名
:to メールの宛先
:from メールの差出人
:bcc メールのBCCアドレス
:cc メールのCCアドレス
:date メールの日付
:mail メールのエンコード形式
{
  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: "..." #(長いので省略)
}

6.2 deliver.action_mailer

キー
:mailer メイラークラス名
:message_id Mail gemが生成したメッセージID
:subject メールの件名
:to メールの宛先
:from メールの差出人
:bcc メールのBCCアドレス
:cc メールのCCアドレス
:date メールの日付
:mail メールのエンコード形式
{
  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: "..." #(長いので省略)
}

7 Active Support

7.1 cache_read.active_support

キー
:key ストアで使われるキー
:hit ヒットしたかどうか
:super_operation 読み出しで#fetchが指定されている場合に:fetch を追加

7.2 cache_generate.active_support

このイベントは、#fetchをブロック付きで使用した場合にのみ使われます。

キー
:key ストアで使われるキー

fetchに渡されたオプションは、ストアへの書き込み時にペイロードとマージされます。

{
  key: 'name-of-complicated-computation'
}

7.3 cache_fetch_hit.active_support

このイベントは、#fetchをブロック付きで使用した場合にのみ使われます。

キー
:key ストアで使われるキー

fetchに渡されたオプションは、ペイロードとマージされます。

{
  key: 'name-of-complicated-computation'
}

7.4 cache_write.active_support

キー
:key ストアで使われるキー

キャッシュストアが独自のキーを追加することがあります。

{
  key: 'name-of-complicated-computation'
}

7.5 cache_delete.active_support

キー
:key ストアで使われるキー
{
  key: 'name-of-complicated-computation'
}

7.6 cache_exist?.active_support

キー
:key ストアで使われるキー
{
  key: 'name-of-complicated-computation'
}

8 Active Job

8.1 enqueue_at.active_job

キー
:adapter ジョブを処理するQueueAdapterオブジェクト
:job Jobオブジェクト

8.2 enqueue.active_job

キー
:adapter ジョブを処理するQueueAdapterオブジェクト
:job Jobオブジェクト

8.3 perform_start.active_job

キー
:adapter ジョブを処理するQueueAdapterオブジェクト
:job Jobオブジェクト

8.4 perform.active_job

キー
:adapter ジョブを処理するQueueAdapterオブジェクト
:job Jobオブジェクト

9 Railties

9.1 load_config_initializer.railties

キー
:initializer config/initializersから読み込まれたイニシャライザへのパス

10 Rails

10.1 deprecation.rails

キー
:message 非推奨機能の警告メッセージ
:callstack 非推奨警告の発生元

11 イベントのサブスクライブ

イベントは簡単にサブスクライブできます。ActiveSupport::Notifications.subscribeをブロック付きで 記述すれば、すべての通知をリッスンできます。

ブロックでは以下の引数を利用できます。

  • イベントの名前
  • イベントの開始時刻
  • イベントの終了時刻
  • イベントのユニークID
  • ペイロード(上の節を参照)
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
  # 自分のコードをここに書く
  Rails.logger.info "#{name} Received!"
end

ブロックの引数を毎回定義しなくても済むよう、次のようなブロック付きのActiveSupport::Notifications::Eventを 簡単に定義できます。

ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
  event = ActiveSupport::Notifications::Event.new *args

  event.name      # => "process_action.action_controller"
  event.duration  # => 10 (in milliseconds)
  event.payload   # => {:extra=>information}

  Rails.logger.info "#{event} Received!"
end

ほとんどのデータはすぐに利用できます。次はデータの取り出し方の例です。

ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
  data = args.extract_options!
  data # { extra: :information }
end

正規表現に一致するイベントだけをサブスクライブすることもできます。 さまざまなイベントを一括でサブスクライブしたい場合に便利です。次は、ActionControllerのイベントをすべて登録する場合の例です。

ActiveSupport::Notifications.subscribe /action_controller/ do |*args|
  # ActionControllerの全イベントをチェック
end

12 カスタムイベントの作成

独自のイベントを自由に追加できます。イベント追加は、ActiveSupport::Notificationsメソッドで すべてまかなえます。namepayload、ブロックを指定してinstrumentを呼び出すだけで追加完了します。 通知は、ブロックが戻ってから送信されます。ActiveSupportでは、開始時刻、終了時刻、 ユニークIDが生成されます。instrument呼び出しに渡されるすべてのデータがペイロードに含まれます。

以下に例を示します。

ActiveSupport::Notifications.instrument "my.custom.event", this: :data do
  # 自分のコードをここに書く
end

これで、次のようにイベントをリッスンできるようになります。

ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
  puts data.inspect # {:this=>:data}
end

独自のイベントを作成するときは、Railsの規則に従ってください。形式は「event.library」を使います たとえば、アプリケーションがツイートを送信するのであれば、イベント名はtweet.twitterとなります。