Tech Racho エンジニアの「?」を「!」に。
  • 開発

Rails: 多機能ベンチマークgem「derailed_benchmarks」README(翻訳)

概要

MITライセンスに基づいて翻訳・公開いたします。

画像はREADMEからの引用です。

Rails: 多機能ベンチマークgem「derailed_benchmarks」README(翻訳)

RailsアプリやRubyアプリのさまざまなベンチマークを取ることができます。

「な・ん・と・か・し・て」

schneems/derailed_benchmarksより

互換性と要件

このgemはRails 3.2以上、Ruby 2.1以上でテストおよび動作確認しています。一部のコマンドはもっと古いRubyでも動くかもしれませんが、すべてのコマンドの動作についてはサポート外です。

一部のベンチマークについては(すべてではありません)、OSでcurlコマンドが実行できることを確認する必要があります。

$ which curl
/usr/bin/curl
$ curl -V
curl 7.37.1 #...

インストール

Gemfileに以下を追加します。

gem 'derailed_benchmarks', group: :development

続いて$ bundle installを実行します。

このgemのコマンドを実行するとき、コマンドの前にbundle execをつけて実行する必要があるかもしれません。

利用可能なプロファイリング用メソッドをすべて追加するには以下も追加します。

gem 'stackprof', group: :development

ライブラリのインストールにはRuby 2.1以降を使わなければなりません。これより古いバージョンのRubyをお使いの方は何を待っているんでしょうね?

使い方

アプリのベンチマークには2とおりの方法があります。derailed_benchmarksでは、Webアプリを起動してベンチマーク中にリクエストを実行することも、Gemfileにある依存関係の静的な情報を取得することもできます。精度についてはアプリを起動する方法の方が常に高くなりますが、productionローカルでアプリを実行できない事情がある場合は静的情報も役に立ちます。

静的なベンチマーク

このセクションでは、アプリを起動する必要なしにGemfileからメモリ情報を取得する方法について説明します。

このセクションのすべてのコマンドは$ derailed bundle:で始まります。

メモリとパフォーマンスの関係については、How Ruby Uses Memoryをご覧ください。

require時に消費するメモリ

プロジェクトにgemを追加するたびに、起動時のメモリ利用量が増加します。gemごとのメモリ使用量を表示するには、以下を実行します。

$ bundle exec derailed bundle:mem

Gemfileにあるgemが読み込まれ、requireされたときに消費するメモリが表示されます。たとえば、mail gemを使っている場合の出力は次のようになります。

$ bundle exec derailed bundle:mem
TOP: 54.1836 MiB
  mail: 18.9688 MiB
    mime/types: 17.4453 MiB
    mail/field: 0.4023 MiB
    mail/message: 0.3906 MiB
  action_view/view_paths: 0.4453 MiB
    action_view/base: 0.4336 MiB

補足: MiBは、IEEEIECでメガバイトを表すシンボルであり、220 バイト / 1024キビバイト(結果は1024バイト)になります。

上の結果からmailが18MiBを消費していることがわかりますが、その大半はmime/typesで占められています。この情報を用いて、不要な依存関係を取り除けます。また、不要なgemが大量のメモリを消費していることに気がついたら、issueをオープンしてgemの作者に知らせてください(再現手順も添えること)。うまくいけばコミュニティがメモリのホットスポットを特定して影響を軽減できるでしょう。パフォーマンスの問題を修正する前に、問題がどこにあるのかを知っておく必要があります。

デフォルトでは、:defaultグループと"production"グループの結果だけを表示します。他のグループの結果を表示するには以下のように実行します。

$ bundle exec derailed bundle:mem development

CUT_OFF=0.3のように指定することで、これよりメモリ使用量の大きいファイルだけを表示できます。余分な情報を抑制したい場合に便利です。

メモ: この方法では、Gemfileにある項目だけが表示されます。アプリそのものに含まれているファイルは含まれません。アプリそのものに含まれるファイルのメモリ使用量を表示するには、bundle exec derailed exec memを使う必要があります。詳しくは後述します。

複数のライブラリで同じファイルがrequireされていることがありますが、Railsではファイルのrequireは一度しか行わないため、そのファイルのコストは、あるファイルを最初にrequireするライブラリにのみ関連します。この点をわかりやすく表示するため、そのファイルが属しているすべての親で重複エントリを表示します。たとえば、mailfogではどちらもmime/typesrequireしているので、アプリでは次のように表示されます。

$ bundle exec derailed bundle:mem
TOP: 54.1836 MiB
  mail: 18.9688 MiB
    mime/types: 17.4453 MiB (Also required by: fog/storage)
    mail/field: 0.4023 MiB
    mail/message: 0.3906 MiB

トップレベルのライブラリ(ここではmail)を削除してもメモリ使用量が減っていないことがわかりました。出力では最初の2つのエントリ以後は省略されます。

fog/core: 0.9844 MiB (Also required by: fog/xml, fog/json, and 48 others)
fog/rackspace: 0.957 MiB
fog/joyent: 0.7227 MiB
  fog/joyent/compute: 0.7227 MiB

fog/corerequireするファイルをすべて表示したい場合は、CUT_OFF=0 bundle exec derailed bundle:memを実行してすべてを出力してから手動でgrepすることもできます。

更新情報: 上の例のmime/typesは残念な結果が出ていますが、現在は修正されています。Gemfileの冒頭に以下を追加することでメモリを削減できます。

gem 'mime-types', [ '~> 2.6', '>= 2.6.1' ], require: 'mime/types/columnar'

require時に作成されるオブジェクト

memory_profilerを使うと、以下を実行することで依存関係がrequireされたときに作成されるオブジェクトの詳しい情報を取得できます。

$ bundle exec derailed bundle:objects

これによって、依存関係の読み込み時に作成されるオブジェクトの詳しい情報が出力されます。

Measuring objects created by gems in groups [:default, "production"]
Total allocated 433895
Total retained 100556

allocated memory by gem
-----------------------------------
  24369241  activesupport-4.2.1
  15560550  mime-types-2.4.3
   8103432  json-1.8.2

メモリ使用量が甚だしいgemを$ bundle exec derailed bundle:mem で特定できたら、そのgemを別のGemfileに追加して$ bundle exec derailed bundle:objectsを実行することで詳細な情報を取得できます。この情報は、コントリビュータやライブラリ作者がオブジェクト作成のホットスポットを特定して排除するのに役立ちます。

デフォルトでは、このタスクは:defaultグループとproductionグループの結果のみを返します。他のグループでの結果が欲しい場合は以下のように実行します。

$ bundle exec derailed bundle:objects development

メモ: この方法では、Gemfileにある項目だけが表示されます。アプリそのものに含まれているファイルは含まれません。アプリそのものに含まれるファイルのメモリ使用量を表示するには、bundle exec derailed exec memを使う必要があります。詳しくは後述します。

アプリの動的ベンチマーク

このベンチマークでは、Railsアプリを起動してそれに対するベンチマークを試みます。$ bundle exec derailed bundle:*による静的なベンチマークと異なり、この方法では特定のアプリの情報も取れます。この方法のメリットは、詳細な情報を取得することでアプリコードに潜む問題を特定できることで、デメリットはアプリをローカルでproduction環境で実行可能にしなければならない点です。アプリによってはローカルでのproduction環境で簡単に実行できないことがあります。

場合によってはmini-profilerを検討してもよいでしょう。mini-profilerの紹介記事については『mini-profiler walkthrough』をご覧ください。これもよいgemであり、derailed-benchmarkで行えるベンチマークとは少々趣が異なります。

production環境をローカルで実行する

動的なベンチマークを試みる前に、アプリをproductionモードで起動できるようにしておく必要があります。productionモードでベンチマークを行う理由は、デプロイ後のパフォーマンスに近づけるためです。本セクションでは、Railsのデファクトのチュートリアルとは異なるヒントをまとめました。

まずはコンソールで実行できるかどうかを試します。

$ RAILS_ENV=production rails console

おそらく、productionのデータベースに接続できないというエラーが表示されるでしょう。この場合、productionデータベースと同じ名前でローカルにデータベースを作成するか、database.ymldevelopmentグループの情報をproductionグループにコピーするかします。

SECRET_KEY_BASEなどのproduction用環境変数がない可能性もあります。この場合、.envファイルをコミットするか(.envを使っている場合)、以下のように環境変数をコマンドで直接追加します。

$ SECRET_KEY_BASE=foo RAILS_ENV=production rails console

production環境をコンソール起動することに成功したら、今度はサーバーをproductionで起動できるようにする必要があります。

$ RAILS_ENV=production rails server

このとき、SSLの強制やドメインのその他の制限を一時的に無効にする必要があるかもしれません。こうした変更を行ったら、デプロイ前にすべて元に戻すことをお忘れなく(うひゃー!)。

rails_12factor gemを使っている場合はSTDOUTから、あるいは以下を実行してlog/production.logから情報を取得できます。

$ tail -f log/production.log

エラーをすべて修正してサーバーをproductionモードで実行できるようになったら、準備はほぼ完了です。

derailed execを実行する

アプリに対して$ derailed execを実行できるようになりました。この後のいくつかのセクションでは、Rackの設定方法や認証済みリクエストを使う方法についてご紹介します。以下を実行することで利用可能なコマンド一覧を表示できます。

$ bundle exec derailed exec --help
  $ derailed exec perf:allocated_objects  # outputs allocated object diff after app is called TEST_COUNT times
  $ derailed exec perf:gc  # outputs GC::Profiler.report data while app is called TEST_COUNT times
  $ derailed exec perf:ips  # iterations per second
  $ derailed exec perf:mem  # show memory usage caused by invoking require per gem
  $ derailed exec perf:objects  # profiles ruby allocation
  $ derailed exec perf:mem_over_time  # outputs memory usage over time
  $ derailed exec perf:test  # hits the url TEST_COUNT times

ここでは個別のコマンドについては解説せず、よくある問題とそれを診断するのに最適なコマンドを見ていくことにします。derailed_benchmarksの設定で使えるすべての環境変数については、この後別途セクションを設けて解説します。

アプリのメモリリークを調べる

アプリのメモリ総量が増加し続けていてメモリリークが疑われる場合、それが本当にどこにもバインドされない「リーク」なのか、単にメモリ使用量が予想より大きいだけなのかを最初に検証したいでしょう。真のメモリリークではメモリ使用量が際限なく増加し続けますが、多くのアプリのメモリ使用量増加はある時点で「頭打ち」になります。これを診断するには以下を実行します。

$ bundle exec derailed exec perf:mem_over_time

このコマンドは、アプリを起動してリクエストを送信し、メモリをSTDOUT(および./tmp以下のファイル)に出力します。出力は次のような感じになります。

$ bundle exec derailed exec perf:mem_over_time
Booting: production
Endpoint: "/"
PID: 78675
103.55078125
178.45703125
179.140625
180.3671875
182.1875
182.55859375
# ...
183.65234375
183.26171875
183.62109375

この結果を見ると、メモリ使用量は増加し続けているものの、183 MiBあたりで横ばいになっています。以下のようにTEST_COUNT=の値を順次増やしながらこのタスクを実行することもできます。

$ TEST_COUNT=5000 bundle exec derailed exec perf:mem_over_time
$ TEST_COUNT=10_000 bundle exec derailed exec perf:mem_over_time
$ TEST_COUNT=20_000 bundle exec derailed exec perf:mem_over_time

適切な総時間が得られるようにTEST_COUNT=の値を適宜調整します。メモリ増加が天井知らずになっていれば、まさしくメモリリークです。これらの出力結果をファイルからGoogle Documentsにコピーしてグラフ化すれば、メモリ使用量が上昇する様子をよりよく把握できます。

生成した結果をtmpファイルに出力したくない場合は、SKIP_FILE_WRITE=1を指定して実行します。

メモリリークが発生していることがある程度確信できるものの、この方法で確認できなかった場合は、後述の環境変数オプションのセクションを参照して、リクエスト送信先のエンドポイントをさまざまに変えながら試してみるとよいでしょう。

メモリリークを解剖する

メモリリークを突き止めることができた場合や、メモリがどこで使われているかを単に知りたい場合は、以下を実行できます。

$ bundle exec derailed exec perf:objects

上のタスクは、アプリにリクエストを送信し、memory_profileを用いてオブジェクトが作成されている箇所を表示します。このコマンドを1度実行した後、TEST_COUNTの値を増やして再実行すれば、すべての要求ごとにオブジェクトが作成されるホットスポットを最初の実行結果と比較しながら見つけることもできます。

$ TEST_COUNT=10 bundle exec derailed exec perf:objects

この操作はコストが高いので、TEST_COUNTの値をなるべく低くしておきたいと思うことでしょう。ホットスポットを突き止められたら、「how ruby uses memory」を読んでオブジェクトのアロケーションを減らす方法をお読みください。

このコマンドは$ bundle exec derailed bundle:objectsと似ていますが、実行時に作成されたオブジェクトも出力される点が異なります。そのため、実際のproductionのパフォーマンスをデバッグする場合に非常に有用です。$ bundle exec derailed bundle:objectsの方はライブラリ作者がデバッグするのに向いています。

ヒープをダンプする

ランタイム時のメモリ使用量の問題がまだ解決できない場合は、ヒープダンプを生成してからheap_inspect(heapy)で分析することもできます。

$ bundle exec derailed exec perf:heap
Booting: production
Heap file generated: "tmp/2015-10-01T12:31:03-05:00-heap.dump"

Analyzing Heap
==============
Generation:  0 object count: 209307
Generation: 35 object count: 31236
Generation: 36 object count: 36705
Generation: 37 object count: 1301
Generation: 38 object count: 8

Try uploading "tmp/2015-10-01T12:31:03-05:00-heap.dump" to http://tenderlove.github.io/heap-analyzer/

ヒープダンプからデータを取得する方法の詳細については、以下を実行します。

$ heapy --help

起動時のメモリ使用量が大きい場合

Rubyのメモリ使用量は、増加の一途をたどるのが普通です。アプリ起動時のメモリ使用量が大きい場合、増加することはあっても減少することはないでしょう。$ derailed bundle:memで取得した依存関係によって使われるメモリ使用量をデバッグするほかに、自分のファイルがメモリ使用量をどれだけ増やしているかを知りたいこともあるでしょう。

ここでご紹介するタスクも本質的には同じですが、アプリにリクエストを1件だけ送信して、requireが直前の1分間にすべて呼び出されるようにします。これを行うには、以下を実行します。

$ bundle exec derailed exec perf:mem

TOP: 54.1836 MiB
  mail: 18.9688 MiB
    mime/types: 17.4453 MiB
    mail/field: 0.4023 MiB
    mail/message: 0.3906 MiB
  action_view/view_paths: 0.4453 MiB
    action_view/base: 0.4336 MiB

表示するメモリ使用量を足切りしたい場合は、CUT_OFF=0.3のように指定することで、これよりメモリ使用量の大きいファイルだけを表示できます。余分な情報を抑制したい場合に便利です。

アプリ起動時のコード量が極端に多い場合は、低レベルのオブジェクト作成を$ derailed exec perf:objectsでデバッグすることをご検討ください。

アプリが遅い

他にも何か起きているかもしれません。既にオブジェクトのアロケーション削減を詳細に検討したのであれば、アプリのどのコードの実行量が最も多いかを知りたいと思うことでしょう。それがわかれば、どの最適化に時間をかければよいかもわかります。

ひとつの方法は、stack profilerの「サンプリング」を使うことです。このプロファイリングは、指定の期間内にどのメソッドが実行されているかを調べて記録します。実行が終わったら、それらのメソッドが呼び出された回数をすべてカウントして、実行時間に占める割合をメソッドごとにパーセントで表示します。この手法では、実行時間の調査におけるオーバーヘッドはほとんどありません。Ruby 2.1以上であれば、stackprofというgemを使って行えます。ご想像のとおり、このgemはderailed_benchmarksでも実行できます。Gemfileにgem "stackprof", group: :developmentを追加して以下を実行します。

$ bundle exec derailed exec perf:stackprof
==================================
  Mode: cpu(1000)
  Samples: 16067 (1.07% miss rate)
  GC: 2651 (16.50%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      1293   (8.0%)        1293   (8.0%)     block in ActionDispatch::Journey::Formatter#missing_keys
       872   (5.4%)         872   (5.4%)     block in ActiveSupport::Inflector#apply_inflections
       935   (5.8%)         802   (5.0%)     ActiveSupport::SafeBuffer#safe_concat
       688   (4.3%)         688   (4.3%)     Temple::Utils#escape_html
       578   (3.6%)         578   (3.6%)     ActiveRecord::Attribute#initialize
      3541  (22.0%)         401   (2.5%)     ActionDispatch::Routing::RouteSet#url_for
       346   (2.2%)         346   (2.2%)     ActiveSupport::SafeBuffer#initialize
       298   (1.9%)         298   (1.9%)     ThreadSafe::NonConcurrentCacheBackend#[]
       227   (1.4%)         227   (1.4%)     block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache
       218   (1.4%)         218   (1.4%)     NewRelic::Agent::Instrumentation::Event#initialize
      1102   (6.9%)         213   (1.3%)     ActiveSupport::Inflector#apply_inflections
       193   (1.2%)         193   (1.2%)     ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper#deprecate_string_options
       173   (1.1%)         173   (1.1%)     ActiveSupport::SafeBuffer#html_safe?
       308   (1.9%)         171   (1.1%)     NewRelic::Agent::Instrumentation::ActionViewSubscriber::RenderEvent#metric_name
       159   (1.0%)         159   (1.0%)     block in ActiveRecord::Result#hash_rows
       358   (2.2%)         153   (1.0%)     ActionDispatch::Routing::RouteSet::Generator#initialize
       153   (1.0%)         153   (1.0%)     ActiveRecord::Type::String#cast_value
       192   (1.2%)         143   (0.9%)     ActionController::UrlFor#url_options
       808   (5.0%)         127   (0.8%)     ActiveRecord::LazyAttributeHash#[]
       121   (0.8%)         121   (0.8%)     PG::Result#values
       120   (0.7%)         120   (0.7%)     ActionDispatch::Journey::Router::Utils::UriEncoder#escape
      2478  (15.4%)         117   (0.7%)     ActionDispatch::Journey::Formatter#generate
       115   (0.7%)         115   (0.7%)     NewRelic::Agent::Instrumentation::EventedSubscriber#event_stack
       114   (0.7%)         114   (0.7%)     ActiveRecord::Core#init_internals
       263   (1.6%)         110   (0.7%)     ActiveRecord::Type::Value#type_cast
      8520  (53.0%)         102   (0.6%)     ActionView::CompiledTemplates#_app_views_repos__repo_html_slim__2939326833298152184_70365772737940

後は個別のメソッドを調べます。

変更の結果パフォーマンスが向上するかをチェックする

コードレベルでのパフォーマンス改善の度合いはマイクロベンチマークである程度見当がつきますが、アプリ全体のスピード向上を知るにはどうしたらよいでしょうか。アプリのパフォーマンス上の変更がどのぐらい有効かを知りたい場合、既存アプリのパフォーマンスと比較できると便利です。そんなときは以下のコマンドが役立ちます。

$ bundle exec derailed exec perf:ips
Endpoint: "/"
Calculating -------------------------------------
                 ips     1.000  i/100ms
-------------------------------------------------
                 ips      3.306  (± 0.0%) i/s -     17.000

このコマンドは、benchmark-ips gemを用いてアプリのエンドポイントにリクエストを送信します。1秒あたりの繰り返し(ips: iterations per second)では、値が大きいほどパフォーマンスが高いことを常に意味します。変更後のコードをこの方法を用いて何度も実行し、続いてトータルのパフォーマンス向上を「ベースライン」コードベース(変更なしの場合)を実行します。実行を何度も繰り返して結果を(標準偏差も含めて)記録すればノイズの抑制に役立ちます。ベンチマークは大変な作業であり、この手法も完璧ではありませんが、何もしないよりはずっとよい結果を得られるのは間違いありません。

気になるのであれば、以下のようにipsなしの純粋なベンチマークを実行することもできます。

$ bundle exec derailed exec perf:test

ただし私ならこの方法は使わないでしょう。ipsありのベンチマークの方が測定方法として良好だからです。

環境変数

どのタスクについても、以下の環境変数を使って設定を与えることができます。

TEST_COUNT: テスト回数の増減

実行回数の多いタスクで、次のようにTEST_COUNTを使って実行回数を指定できます。

$ TEST_COUNT=100_000 bundle exec derailed exec perf:test

WARM_COUNT: 測定前にアプリをウォーミングアップする回数

アプリを長時間に渡って測定する場合、特にJITを使っている場合は、測定時間とは別の「ウォーミングアップ」期間をアプリに与えるものです。WARM_COUNTを用いて、測定開始前のアプリ呼び出し回数を指定できます。

$ WARM_COUNT=5_000 bundle exec derailed exec perf:test
Warming up app: 5000 times
# ...

PATH_TO_HIT: リクエスト送信先のエンドポイントの変更

デフォルトのタスクでは、ルート/に対してリクエストを送信します。別のURLにリクエストを送信したい場合は、PATH_TO_HITを使います。たとえば、users/newを叩くには以下を実行します。

$ PATH_TO_HIT=/users/new bundle exec derailed exec perf:mem

この方法では完全なURLも指定できます。たとえば以下はサブドメインのエンドポイントを叩きます。

$ PATH_TO_HIT=http://subdomain.lvh.me:3000/users/new bundle exec derailed exec perf:mem

注意: 完全なURLは、USE_SERVER変数と併用できません。

HTTPヘッダの設定

次のようにHTTP_<ヘッダ名>環境変数を設定することで、HTTPヘッダを指定できます。

$ HTTP_AUTHORIZATION="Basic YWRtaW46c2VjcmV0\n" \
  HTTP_USER_AGENT="Mozilla/5.0" \
  PATH_TO_HIT=/foo_secret bundle exec derailed exec perf:ips

USE_SERVER: 実際のWebサーバーの指定

すべてのテストは「Webサーバーなし」で実行されます(デフォルトではRack::Mockを直接使います)。Webサーバーを使いたい場合は、USE_SERVERにRack::Server互換サーバー(webrickなど)を設定します。

$ USE_SERVER=webrick bundle exec derailed exec perf:mem

pumaも指定できます。

$ USE_SERVER=puma bundle exec derailed exec perf:mem

この指定によってWebサーバーが起動し、メモリ内ではなくcurlを使ってリクエストを送信します。これは、パフォーマンスの問題がWebサーバーに関連すると考えられる場合に便利です。

メモ: この方法では指定のWebサーバーをRackに直接接続するため、設定済みのpuma.configファイルなどは使われません。設定ファイルを使いたい方からのアイデアやプルリクをお待ちしています。

ActiveRecordを除外する

derailed_benchmarksは、依存関係としてActiveRecord gemが含まれている場合はデフォルトで読み込みます。ActiveRecordはRailsのデフォルトgemなので、rails gemを使えば読み込まれます。別のORMを使っている場合は、railtiesだけを読み込むようにするか、DERAILED_SKIP_ACTIVE_RECORDフラグを設定します。

$ DERAILED_SKIP_ACTIVE_RECORD=true

別の環境で実行する

デフォルトではproduction環境でテストを実行しますが、ローカルのRAILS_ENVproductionに設定された環境でアプリが動かない場合は次のように簡単に変更できます。

$ RAILS_ENV=development bundle exec derailed exec perf:mem

perf.rake

derailed_benchmarksをカスタマイズしたい場合は、ベンチマークを行うプロジェクトのルートディレクトリにperf.rakeファイルを作成する必要があります。

rakeを直接使ってベンチマークを実行できます。

$ cat <<  EOF > perf.rake
  require 'bundler'
  Bundler.setup

  require 'derailed_benchmarks'
  require 'derailed_benchmarks/tasks'
EOF

perf.rakeファイルの内容は次のような感じになります。

$ cat perf.rake
  require 'bundler'
  Bundler.setup

  require 'derailed_benchmarks'
  require 'derailed_benchmarks/tasks'

上の設定を使うと、アプリより先にベンチマークが読み込まれます。これはベンチマークによっては重要なことがありますが、さほど重要でないこともあります。この設定では、ベンチマークが不要な場合に誤って読み込まれることも防げます。

続いて、rakeを使ってコマンドを実行できます。

$ rake -f perf.rake -Tを使って、実行可能なタスクの一覧を見つけることもできます。このコマンドは本質的に、perf.rakeファイルを用いてすべてのタスク一覧を表示しているだけです。

$ rake -f perf.rake -T

Rackのセットアップ

Railsをお使いであれば、特別な設定は不要です。Rackをお使いの場合は、アプリの起動方法の指定が必要です。以下のようにperf.rakeファイルにタスクを追加します。

namespace :perf do
  task :rack_load do
    DERAILED_APP = # ここにコードを書く
  end
end

DERAILED_APP定数には、Raskアプリの名前を設定します。詳しくはプルリク#1をご覧ください。

perf.rakeの設定例は次のようになります。

# perf.rake

require 'bundler'
Bundler.setup

require 'derailed_benchmarks'
require 'derailed_benchmarks/tasks'

namespace :perf do
  task :rack_load do
    require_relative 'lib/application'
    DERAILED_APP = MyApplication::Routes
  end
end

認証

認証が行われているエンドポイントをテストする場合は、認証のバイパス方法をタスクで指定する必要があります。認証はDerailedBenchmarks.authオブジェクトで管理されます。derailed_benchmarksにはDeviseのサポートが組み込まれています。他の認証方法を使っている場合は、独自の認証ストラテジーを記述することもできます。

テストで認証を有効にするには以下を実行します。

$ USE_AUTH=true bundle exec derailed exec perf:mem

認証のカスタマイズ方法については後述します。

Deviseでの認証

Deviseをお使いの場合は、Devise gemの存在を検出して自動で読み込まれる組み込みの認証ヘルパーを使えます。

プロジェクトのルートディレクトリにperf.rakeファイルを作成します。

$ cat perf.rake

ログインユーザーをカスタマイズするには、perf.rakeファイルで以下のように設定します。

DerailedBenchmarks.auth.user = -> { User.find_or_create!(twitter: "schneems") }

有効なユーザーを指定する必要があります。user.rbでのバリデーション方法によっては、パラメータを変える必要があるかもしれません。

Userモデルを使わない認証を行う場合は、独自の認証ストラテジーを書く必要があります。

独自の認証ストラテジー

独自の認証ストラテジーを実装するには、auth_helper.rbを継承するクラスの作成と、setupメソッドとcallメソッドの実装が必要です。Devise認証ヘルパーのサンプルコードについてはauth_helpers/devise.rbをご覧ください。以下のコードをperf.rakeファイルに書けます。

class MyCustomAuth < DerailedBenchmarks::AuthHelper
  def setup
    # 初期化コードをここに書く
  end

  def call(env)
    # リクエストごとのログを何か出力する
    app.call(env)
  end
end

Deviseのストラテジーは、Rackリクエストの内部でテストモードを有効にしてスタブユーザーを挿入することで動作するようになります。Deviseを使っていない場合は、この部分のロジックを複製して独自の認証スキームを作成する必要があります。

クラスを作成したら、クラスの新しいインスタンスにDerailedBenchmarks.authを設定する必要があります。perf.rakeファイルに以下を追加します。

DerailedBenchmarks.auth = MyCustomAuth.new

これにより、USE_AUTH環境変数を使って設定されたすべてのリクエストで、MyCustomAuth#callメソッドが呼び出されるようになります。

ライセンス

MIT

献辞

コマンドの多くは他のライブラリのラッパーですので、チェックしてみてください。$ rake perf:setupのRails初期化コードの一部については、@tenderloveのとあるプロジェクトのコードを使いましたので、ここにお礼を申し上げます。

アリガトソレジャマタ @schneems

関連記事

ベンチマークの詳しい理解と修正のコツ(翻訳)

Rails: RedisキャッシュとRackミドルウェアでパフォーマンスを改善(翻訳)

Rails: メモリ使用量を制限してHerokuのR14エラー修正&費用を節約した話(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。