プロファイリング

パフォーマンスの問題を追跡しやすくするために、GitLabには一連のプロファイリングツールが用意されています。これらの中にはデフォルトで利用できるものもあれば、明示的に有効にする必要があるものもあります。

URLのプロファイリング

Gitlab::Profiler.profile メソッドとそれに対応するbin/profile-url スクリプトがあり、特定の URL に対する GET または POST リクエストを、匿名ユーザー (デフォルト) あるいは特定のユーザーとしてプロファイリングすることができます。

注意:プロファイラの最初の引数は、完全な URL (インスタンスのホスト名を含む) か絶対パス (先頭のスラッシュを含む) です。

スクリプトを使用する場合、引数を指定しないことで、コマンドライン・ドキュメンテーションを利用できます。

インタラクティブなコンソール・セッションでこのメソッドを使用すると、そのコンソール・セッション内でのアプリケーション・コードの変更がプロファイラの出力に反映されます。

使用例:

Gitlab::Profiler.profile('/my-user')
# Returns a RubyProf::Profile for the regular operation of this request
class UsersController; def show; sleep 100; end; end
Gitlab::Profiler.profile('/my-user')
# Returns a RubyProf::Profile where 100 seconds is spent in UsersController#show

作成者を必要とするルートでは、Gitlab::Profilerにユーザーを提供する必要があります。 このようにできます:

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first)

Gitlab::Profiler.profilelogger: キーワード引数を渡すと、ActiveRecord と ActionController のログ出力がそのロガーに送られます。 その他のオプションはメソッドのソースに記載されています。

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, logger: Logger.new(STDOUT))

RubyProfプリンタもあります:Gitlab::Profiler::TotalTimeFlatPrinter. これはRubyProf::FlatPrinterと同じように動作しますが、min_percent オプションはメソッド自体の時間ではなく、メソッドの合計時間で動作します。 (これは、ライブラリコードでほとんどの時間を費やすことがよくありますが、これはアプリケーションの呼び出しから来るためです。) また、max_percent オプションもあり、有用でない内部呼び出しをフィルタリングするのに役立ちます (ActionDispatch::Integration::Session#processのようなものです)。

Gitlab::Profiler.print_by_total_timeという便利な方法があります:

result = Gitlab::Profiler.profile('/my-user')
Gitlab::Profiler.print_by_total_time(result, max_percent: 60, min_percent: 2)
# Measure Mode: wall_time
# Thread ID: 70005223698240
# Fiber ID: 70004894952580
# Total: 1.768912
# Sort by: total_time
#
#  %self      total      self      wait     child     calls  name
#   0.00      1.017     0.000     0.000     1.017       14  *ActionView::Helpers::RenderingHelper#render
#   0.00      1.017     0.000     0.000     1.017       14  *ActionView::Renderer#render_partial
#   0.00      1.017     0.000     0.000     1.017       14  *ActionView::PartialRenderer#render
#   0.00      1.007     0.000     0.000     1.007       14  *ActionView::PartialRenderer#render_partial
#   0.00      0.930     0.000     0.000     0.930       14   Hamlit::TemplateHandler#call
#   0.00      0.928     0.000     0.000     0.928       14   Temple::Engine#call
#   0.02      0.865     0.000     0.000     0.864      638  *Enumerable#inject

プロフィールをHTML形式で印刷するには、次の例を使用します:

result = Gitlab::Profiler.profile('/my-user')

printer = RubyProf::CallStackPrinter.new(result)
printer.print(File.open('/tmp/profile.html', 'w'))

GitLab-Profilerはこれをベースに、複数の URL に対して一つの YAML ファイルで設定できるようにしたり、プロファイルやログ出力を S3 にアップロードできるようにしたりするなど、いくつかの機能を追加したプロジェクトです。

GitLab.comについては、こちらで最新の結果をご覧いただけます(GitLabチームメンバー限定):https://redash.gitlab.com/dashboard/gitlab-profiler-statistics

シャーロック

SherlockはGitLabに組み込まれたカスタムプロファイリングツールです。SherlockはGitLabを開発モードで実行_し、_環境変数ENABLE_SHERLOCK を空でない値に設定した_場合のみ_利用できます。 例えば、以下のようになります:

ENABLE_SHERLOCK=1 bundle exec rails s

記録された取引は、/sherlock/transactionsにアクセスして確認できます。

弾丸

Bulletは、N+1クエリの問題を追跡するために使用できるGemです。 Bulletセクションはパフォーマンス・バーに表示されます。

Bullet

Bulletはかなりの量のロギングノイズを追加するため、ロギングはデフォルトでは無効になっています。 ロギングを有効にするには、GitLabを起動する前に環境変数ENABLE_BULLET に空でない値を設定します。 たとえば、次のようにします:

ENABLE_BULLET=true bundle exec rails s

Bulletはクエリの問題をRailsログとChromeコンソールの両方に記録します。

BulletでN+1 クエリを見つけたときのフォローアップとして、リグレッションを防ぐためにQueryRecoderテストを書くことを検討してください。

パフォーマンスに影響する設定

  1. development 環境ではデフォルトでホットリロードが有効になっていますが、ホットリロードはシングルスレッドで行われるため、Railsはリクエストごとにファイルの変更をチェックし、競合ロックが発生する可能性があります。
  2. development 環境では、リクエストが発生すると、コードを遅延してロードすることができ、その結果、最初のリクエストは常に遅くなります。

プロファイリング/ベンチマークのためにこれらの機能を無効にするには、GitLabを起動する前にRAILS_PROFILE 環境変数をtrue に設定します。 例えばGDKを使用する場合:

  • GDKのルートディレクトリにファイルenv.runit を作成します。
  • env.runit ファイルにexport RAILS_PROFILE=true を追加してください。
  • でGDKを再起動します。gdk restart

この環境変数は開発モードにのみ適用されます。