OSS活動記 #3 - reactionview - レンダリング時間を出力するPRがめちゃめちゃ良いのでさらに魔改造してクエリ発行数も追加してみた

対象リポジトリ

github.com

レンダリング時間を出力するPR

github.com

PRの内容と状況を見てみた

reactionviewのデバッグモードを使うとviewテンプレートとかpartialテンプレートで出力されたHTML要素に枠線追加して可視化してくれる。PRはこの情報にレンダリング時間を追加してくれるもの。どうやって実現しているのかが気になったので実装を覗いてみた。

herbとreactionviewを使えばHTMLのerbテンプレートに対してASTで処理ができる。レンダリング時間を計測するrubyコードを含んだerbタグを動的に追加していた。ざっくり以下のようなタグがerbテンプレートの先頭と末尾に追加されるイメージ。

<% __reactionview_timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) %>
<% __reactionview_timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) %>
<% __reactionview_timing_ms = ((__reactionview_timing_end - __reactionview_timing_start) * 1000).round(2) %>

さて、この計測した値をどうやってブラウザで表示される画面まで持ってくるかというところで、PRの実装では以下の流れになっていた。

  1. 計測値をスレッド固有データとして格納 e.g. Thread.current[:reactionview_timings][__reactionview_render_id] = { ... }
  2. middlewareを使ってスレッド固有データに格納したHashをJSONに変換してscriptタグに出力
  3. 画面上で動くスクリプトJSONを含むscriptタグからJSON値を回収

うーん、スレッド固有データとかmiddleware経由するとか、グローバルに近いスコープで値を移しているのが少し気になる。

実装は気になるところあるけど実現している機能はめちゃめちゃ良いのではよマージされんかなと思っているのだが、2ヶ月前にDraft PRとしてオープンされており放置されているように見受けられる。とりあえず機能を実現してみたPoC的なPRだったのかもしれない。

よりシンプルに実現できないか自分で実装してみる

middlewareを経由しなくても解決できそうな気がしたので実装してみた。PRの魔改造

以下のコミットを作ってみた: https://github.com/kozy4324/reactionview/commit/f0b72e2d161c8663632539c97dc55f75735c53cb

こういったscriptタグで解決できるのではないかという。scriptの即時実行 + rubyの値はdata属性に出力して document.currentScript.dataset から取得すればええやろ派。あと画面表示は data-herb-debug-outline-typeview or partial なもの(これがデバッグモードで枠線表示される要素になる)を探してその data-herb-debug-file-name に追記してみた。これは手抜き実装。

<script data-herb-timing-duration="3.14"
        data-herb-queries-count="0"
        data-herb-cached-queries-count="0"
        data-herb-target-debug-file-full-path="/Users/kozy4324/work/blog-app/app/views/layouts/application.html.erb">
(() => {
  const fullPath = document.currentScript.dataset.herbTargetDebugFileFullPath;
  const targetElm = document.querySelector(`[data-herb-debug-outline-type~="view"][data-herb-debug-file-full-path="${fullPath}"]`) ||
                    document.querySelector(`[data-herb-debug-outline-type~="partial"][data-herb-debug-file-full-path="${fullPath}"]`);
  if (targetElm) {
    const timingDuration = document.currentScript.dataset.herbTimingDuration;
    const queriesCount = document.currentScript.dataset.herbQueriesCount;
    const cachedQueriesCount = document.currentScript.dataset.herbCachedQueriesCount;
    targetElm.dataset.herbDebugFileName += ` (${timingDuration} ms, ${queriesCount} queries, ${cachedQueriesCount} cached)`;
  }
})();
</script>

追加でクエリ発行数も追加してみた

Rails 7.2 からログ出力にクエリ発行数が追加されている。

Add queries count to template rendering instrumentation by fatkodima · Pull Request #51457 · rails/rails · GitHub

このPR見ると、グローバルな ActiveRecord::RuntimeRegistry から取得できる。これも追加したら良いのでは?というアイデアが降ってきたので実装した。

これ良くない?めちゃくちゃ良くないですか...?

というわけで色々まとめて作者にコメントしておいた。

https://github.com/marcoroth/reactionview/pull/55#issuecomment-3673672277

まとめ

この機能がリリースされると嬉しいな、

もしこのレンダリング時間表示+クエリ発行数表示を試してみたいという場合はGemfileに以下のように記述して私のブランチから直接インストールすれば動かせます。フィードバックなどあればよろしくお願いします。

gem "reactionview", "~> 0.2.0", github: "kozy4324/reactionview", branch: "render-times-and-queries-count-with-build"