プロファイリング

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

URL のプロファイリング

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

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

デフォルトでは、レポート・ダンプは一時ファイルに保存され、Stackprof API を使用して操作できます。

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

対話的なコンソールセッションでこのメソッドを使用すると、そのコンソールセッション内でアプリケーションコードに加えられた変更がプロファイラの出力に反映されます。

使用例:

Gitlab::Profiler.profile('/my-user')
# Returns the location of the temp file where the report dump is stored
class UsersController; def show; sleep 100; end; end
Gitlab::Profiler.profile('/my-user')
# Returns the location of the temp file where the report dump is stored
# 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))

profiler_options ハッシュを渡すと、サンプリングデータの出力ファイル (out) を設定できます。例えば

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, profiler_options: { out: 'tmp/profile.dump' })

GitLab::Profiler レポートの読み込み

サンプリング・データに対してStackprofを実行すると、時間が費やされた場所の要約を得ることができます。例えば

stackprof tmp/profile.dump

サンプリング・データの例:

==================================
  Mode: wall(1000)
  Samples: 8745 (6.92% miss rate)
  GC: 1399 (16.00%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      1022  (11.7%)        1022  (11.7%)     Sprockets::PathUtils#stat
       957  (10.9%)         957  (10.9%)     (marking)
       493   (5.6%)         493   (5.6%)     Sprockets::PathUtils#entries
       576   (6.6%)         471   (5.4%)     Mustermann::AST::Translator#decorator_for
       439   (5.0%)         439   (5.0%)     (sweeping)
       630   (7.2%)         241   (2.8%)     Sprockets::Cache::FileStore#get
       208   (2.4%)         208   (2.4%)     ActiveSupport::FileUpdateChecker#watched
       206   (2.4%)         206   (2.4%)     Digest::Instance#file
       544   (6.2%)         176   (2.0%)     Sprockets::Cache::FileStore#safe_open
       176   (2.0%)         176   (2.0%)     ActiveSupport::FileUpdateChecker#max_mtime
       268   (3.1%)         147   (1.7%)     ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache
       140   (1.6%)         140   (1.6%)     ActiveSupport::BacktraceCleaner#add_gem_filter
       116   (1.3%)         116   (1.3%)     Bootsnap::CompileCache::ISeq.storage_to_output
       160   (1.8%)         113   (1.3%)     Gem::Version#<=>
       109   (1.2%)         109   (1.2%)     block in <main>
       108   (1.2%)         108   (1.2%)     Gem::Version.new
       131   (1.5%)         105   (1.2%)     Sprockets::EncodingUtils#unmarshaled_deflated
      1166  (13.3%)          82   (0.9%)     Mustermann::RegexpBased#initialize
        82   (0.9%)          78   (0.9%)     FileUtils.touch
        72   (0.8%)          72   (0.8%)     Sprockets::Manifest.compile_match_filter
        71   (0.8%)          70   (0.8%)     Grape::Router#compile!
        91   (1.0%)          65   (0.7%)     ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#query
        93   (1.1%)          64   (0.7%)     ActionDispatch::Journey::Path::Pattern::AnchoredRegexp#accept
        59   (0.7%)          59   (0.7%)     Mustermann::AST::Translator.dispatch_table
        62   (0.7%)          59   (0.7%)     Rails::BacktraceCleaner#initialize
      2492  (28.5%)          49   (0.6%)     Sprockets::PathUtils#stat_directory
       242   (2.8%)          49   (0.6%)     Gitlab::Instrumentation::RedisBase.add_call_details
        47   (0.5%)          47   (0.5%)     URI::RFC2396_Parser#escape
        46   (0.5%)          46   (0.5%)     #<Class:0x00000001090c2e70>#__setobj__
        44   (0.5%)          44   (0.5%)     Sprockets::Base#normalize_logical_path

フレームグラフも作成できます:

stackprof --d3-flamegraph tmp/profile.dump > flamegraph.html

詳細はStackprofのドキュメントを参照してください。

スピードスコープのフレームグラフ

パフォーマンスバーのフレームグラフサンプリングモードボタンを選択するか、performance_bar=flamegraph パラメータをリクエストに追加することで、特定の URL のフレームグラフを生成できます。

Speedscope

ビューの詳細については、Speedscope のドキュメントを参照してください。

様々なサンプリング・モードについてはStackprofのドキュメントを参照してください。

これは、パフォーマンス・バーにアクセスできるすべてのユーザーに対して有効です。

弾丸

Bulletは、N+1クエリの問題を追跡するために使用できるGemです。クエリの問題をRailsログとブラウザコンソールに記録します。Bulletセクションはパフォーマンスバーに表示されます。

Bullet

Bulletはデフォルトでは開発モードでのみ有効です。ただし、Bulletのロギングはノイズが多いため、ロギングは無効になっています。Bulletとそのロギングを設定するには:

  • 環境上で手動でBulletを有効または無効にするには、config/gitlab.yml に以下の行を追加し、必要に応じてenabled の値を変更してください:

     bullet:
       enabled: false
    
  • Bulletロギングを有効にするには、GitLabを起動する前にENABLE_BULLET 環境変数に空でない値を設定します:

     ENABLE_BULLET=true bundle exec rails s
    

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

システム統計

プロファイリング中やプロファイリング後に、メモリ消費量やCPUに費やされた時間、ガベージコレクタの統計情報など、Ruby仮想マシンのプロセスに関する詳細な情報を取得したくなることがあります。これらは様々なツールで個別に簡単に作成できますが、利便性のために、このデータを JSON ペイロードとしてエクスポートするサマリーエンドポイントが追加されました:

curl localhost:3000/-/metrics/system | jq

出力例です:

{
  "version": "ruby 2.7.2p137 (2020-10-01 revision a8323b79eb) [x86_64-linux-gnu]",
  "gc_stat": {
    "count": 118,
    "heap_allocated_pages": 11503,
    "heap_sorted_length": 11503,
    "heap_allocatable_pages": 0,
    "heap_available_slots": 4688580,
    "heap_live_slots": 3451712,
    "heap_free_slots": 1236868,
    "heap_final_slots": 0,
    "heap_marked_slots": 3451450,
    "heap_eden_pages": 11503,
    "heap_tomb_pages": 0,
    "total_allocated_pages": 11503,
    "total_freed_pages": 0,
    "total_allocated_objects": 32679478,
    "total_freed_objects": 29227766,
    "malloc_increase_bytes": 84760,
    "malloc_increase_bytes_limit": 32883343,
    "minor_gc_count": 88,
    "major_gc_count": 30,
    "compact_count": 0,
    "remembered_wb_unprotected_objects": 114228,
    "remembered_wb_unprotected_objects_limit": 228456,
    "old_objects": 3185330,
    "old_objects_limit": 6370660,
    "oldmalloc_increase_bytes": 21838024,
    "oldmalloc_increase_bytes_limit": 119181499
  },
  "memory_rss": 1326501888,
  "memory_uss": 1048563712,
  "memory_pss": 1139554304,
  "time_cputime": 82.885264633,
  "time_realtime": 1610459445.5579069,
  "time_monotonic": 24001.23145713,
  "worker_id": "puma_0"
}
note
このエンドポイントは Rails Web Worker でのみ使用できます。Sidekiq ワーカーはこの方法では検査できません。

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

アプリケーションの設定

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

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

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

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

GC設定

Rubyのガベージコレクタ(GC) は、アプリケーションのパフォーマンスに直接影響するさまざまな環境変数によって調整することができます。

以下の表に、これらの変数とデフォルト値を示します。

環境変数デフォルト値 RUBY_GC_HEAP_INIT_SLOTS10000RUBY_GC_HEAP_FREE_SLOTS4096RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO0.20RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO0.40RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO0.65RUBY_GC_HEAP_GROWTH_FACTOR1.8RUBY_GC_HEAP_GROWTH_MAX_SLOTS0 (disable)RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR2.0RUBY_GC_MALLOC_LIMIT(_MIN)(16 * 1024 * 1024 /* 16MB */)RUBY_GC_MALLOC_LIMIT_MAX(32 * 1024 * 1024 /* 32MB */)RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR1.4RUBY_GC_OLDMALLOC_LIMIT(_MIN)(16 * 1024 * 1024 /* 16MB */)RUBY_GC_OLDMALLOC_LIMIT_MAX(128 * 1024 * 1024 /* 128MB */)RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR1.2を参照してください。

(ソース)

GitLabは、アプリケーションのパフォーマンスを高速化したり、メモリ要件を下げたり、あるいはその両方を行うために、これらの設定を変更することがあります。

scripts/perf/gc/collect_gc_stats.rb スクリプトを実行することで、これらの設定がアイドル状態のGitLabインスタンスのGCパフォーマンスやメモリ使用量、アプリケーション起動時間にどのような影響を与えるかを確認することができます。GCの統計と一般的なタイミングデータをCSVとして標準出力します。