rack-mini-profiler の flamegraph から機械可読な stackprof データを抜いて集計する

rack-mini-profiler が動いている環境では ?pp=flamegraph でフレームグラフが見れる。
ただしこの出力は人間用の speedscope HTML なので、AI/エージェントには読めない。
そこで、その HTML 内に埋め込まれた var graph = {...} を抜く。これは生の stackprof JSON (raw 形式: mode / interval / samples / frames …) なので、機械可読に集計できる。
何も追加で仕込まずに済み、再現可能で、対策の before/after を同じ方法で定量比較できるのが利点。

手順1: flamegraph HTML を取得して graph JSON を抜く
wall モードで取得する (cpu / object / custom も可)。認証が要るページなら Cookie 等を適宜付ける。
bash
Copied!
curl -s "https://example.com/<PATH>?pp=flamegraph&flamegraph_mode=wall&flamegraph_sample_rate=1" \
-o /tmp/flame.html
# var graph = {...}; の 1 行を取り出して JSON 化
grep -m1 'var graph = ' /tmp/flame.html | sed -E 's/^[[:space:]]*var graph = //; s/;[[:space:]]*$//' > /tmp/graph.json

手順2: 集計する
frames の samples が self、 total_samples が total。ホットスポットは self% で見る。
python
Copied!
import json; g=json.load(open('/tmp/graph.json')); S=g['samples']
rows=[(v.get('name'),v.get('samples',0),v.get('total_samples',0)) for v in g['frames'].values()]
for n,s,t in sorted(rows,key=lambda r:-r[1])[:25]:
print(f"{s:5d} {100*s/S:4.1f}% tot={t:5d} {n}")
print("wall~%.0fms samples=%d gc=%d"%(S*g['interval']/1000, S, g['gc_samples']))

注意
middleware フレームは total ≒ 100% になる (リクエスト全体をラップしているだけ) ので、ボトルネック特定は total ではなく self を見ること。
self 時間を用途ごと (例: DB/ORM の属性アクセス、文字列生成、GC の (sweeping) / (marking) 、IO 待ちの Kernel.select など) に束ねると傾向が掴みやすい。束ね方はアプリ依存。
flamegraph_mode は wall / cpu / object / custom。 flamegraph_sample_rate はサンプリング間隔 (ms)。
全オプションは ?pp=help で確認できる。

---
nana なんかそもそもjsonだけを返してくれるオプションがあるといいんだけど。