perf Flame Graph on Ubuntu — CPU使用率をフレームグラフで可視化

perf Flame Graph on Ubuntu — CPU使用率をフレームグラフで可視化 Linux知識

「どの関数がCPUを食っているのか、一目で分かればいいのに」——そんな悩みに直球で答えてくれるのが Flame Graph(フレームグラフ)です。Linux標準のプロファイラ perf と Brendan Gregg 氏の FlameGraph スクリプトを組み合わせると、CPU使用率をSVGの「炎」のかたちで可視化できます。

結論から言うと、perf record → perf script → stackcollapse-perf.pl → flamegraph.pl の4ステップで作れます。実際に Ubuntu 24.04 のコンテナで CPU負荷プログラムをプロファイルしたところ、2,344サンプルから生成したフレームグラフで、hash_mix という関数が全体の89.12%を占めていることが一目で分かりました。この記事ではインストールからSVG生成・読み方まで、すべて実際のコマンド出力付きで解説します。

この記事のポイント

  • linux-tools-generic を入れると perf が使える(Ubuntu 24.04 で perf 6.8.12 を実測)
  • perf stat でCPU時間・コンテキストスイッチ・ページフォルトをコマンド1本で把握できる
  • フレームグラフは perf record → perf script → stackcollapse-perf.pl → flamegraph.pl の4ステップで生成できる
  • 生成したSVGはブラウザで開くだけで、クリックでズームできるインタラクティブな図になる
  • perf record はカーネルのサンプリングを行うため root(コンテナなら --privileged)が必要

検証環境について

本記事のコマンドは Ubuntu 24.04 LTS(Docker公式イメージ ubuntu:24.04、arm64)の --privileged コンテナで実測しています。perf record でカーネルのサンプリングを取るには root 権限が必要です。VPSの実機なら sudo で代替できます。コンテナ特有のハマりどころは記事後半の「よくあるエラー」で解説します。

目次

  1. Flame Graph とは何か
  2. 前提環境と perf ツールのインストール
  3. perf stat でCPU使用率を計測する
  4. Flame Graph を生成する(4ステップ)
  5. Flame Graph の見方
  6. Ubuntu バージョン別 perf 対応比較
  7. よくあるエラーと解決策
  8. まとめ

Flame Graph とは何か

Flame Graph は、プログラムの コールスタック(関数呼び出しの積み重なり)を一定間隔でサンプリングし、各関数がCPUを占有していた割合を「幅」で表したチャートです。Brendan Gregg 氏(Netflix・Sun Microsystems 出身)が考案しました。

横軸は「CPUに占めた時間の割合」で、左右の並び順そのものに意味はありません(アルファベット順)。縦軸は「スタックの深さ」です。一番上の段(フレーム)が実際にCPUを使っていた関数、その下が呼び出し元です。幅が広いフレームほどCPU時間を多く消費しているので、一目でボトルネックを特定できるのが最大の魅力です。

生成の流れは次の4ステップに整理できます。

Flame Graph 生成の全体フロー(概念図)
Flame Graph 生成の全体フロー(概念図)

前提環境と perf ツールのインストール

①動作確認済み環境

この記事では次の環境で動作確認しています。

項目
OS Ubuntu 24.04.4 LTS(Docker公式イメージ ubuntu:24.04 / arm64)
perf バージョン 6.8.12(linux-tools-generic 6.8.0-124.124)
コンパイラ gcc 13.3.0
FlameGraph Brendan Gregg 版(GitHub: brendangregg/FlameGraph)
確認日 2026-06-14

②perf をインストールする

Ubuntu では linux-tools-generic パッケージで perf が入ります。linux-tools-common は perf のラッパーやマニュアルを含む共通パッケージなので、合わせて入れておきます。




ubuntu@linuxlab: ~
$ sudo apt update && sudo apt install -y linux-tools-common linux-tools-generic
Selecting previously unselected package linux-tools-6.8.0-124.
Preparing to unpack …/linux-tools-6.8.0-124_6.8.0-124.124_arm64.deb …
Setting up linux-tools-common (6.8.0-124.124) …
Setting up linux-tools-6.8.0-124-generic (6.8.0-124.124) …
Setting up linux-tools-generic (6.8.0-124.124) …
$ perf –version
perf version 6.8.12

インストール後、perf --versionperf version 6.8.12 が表示されれば成功です(ubuntu:24.04 で実測)。VPSの実機ならこれだけで動きます。なお、Dockerコンテナ(特に Mac の Docker Desktop)では「perf not found for kernel …」という警告が出ることがあります。その理由と回避策は後半で説明します。

linux-tools-generic で perf を導入した実ログ(Ubuntu 24.04)実測
linux-tools-generic で perf を導入した実ログ(Ubuntu 24.04)実測

perf stat でCPU使用率を計測する

perf stat は、コマンドの実行中にCPU使用時間・コンテキストスイッチ・ページフォルトなどを まとめて計測してくれるコマンドです。フレームグラフを作る前に「そもそもどれくらいCPUを使っているのか」を1行で把握するのに便利です。

①基本的な使い方

例として、bashで200万回のループを回した処理を計測してみます。




ubuntu@linuxlab: ~
$ perf stat -e cpu-clock,task-clock,page-faults,context-switches \
bash -c ‘for i in $(seq 1 2000000); do :; done’

Performance counter stats for ‘bash -c for i in …’:

2367.61 msec cpu-clock # 1.009 CPUs utilized
2367.87 msec task-clock # 1.009 CPUs utilized
54440 page-faults # 22.994 K/sec
607 context-switches # 256.377 /sec

2.347298501 seconds time elapsed
2.246283000 seconds user
0.151552000 seconds sys

読み方のポイントは次の通りです。

  • cpu-clock(2,367.61ms):この処理が実際にCPUを使った時間の合計
  • page-faults(54,440件):メモリのページを新たに確保した回数。seq が大量のページを要求するため多めに出ます
  • context-switches(607件):他プロセスへCPUを譲り渡した回数。数が極端に多いと「I/O待ちが多い」サインになることがあります
  • user 2.246s / sys 0.151s:ユーザー空間の計算がほとんどで、カーネル処理(システムコール)はわずか、と読めます
著者アイコン
著者アイコン

正直、初めて perf stat の出力を見たとき「cpu-clock と task-clock の違いは?」で詰まりました。ざっくり、task-clock は「このタスクが走っていた壁時計時間(複数スレッドなら合算)」、cpu-clock は「CPUコアの演算時間」と覚えると区別しやすいです。今回はシングルスレッドなのでほぼ同じ値になっています。

perf stat の実測値(bashループ 200万回)実測データ
perf stat の実測値(bashループ 200万回)実測データ

Flame Graph を生成する(4ステップ)

ここからが本題です。フレームグラフの生成は 4つのコマンドを順に流すだけです。今回は、わざとCPUを食う関数を3つ(ハッシュ計算・整数演算・平方根計算)持つ小さなC言語プログラム work をプロファイル対象にしました。

①FlameGraph スクリプトと対象プログラムを準備する

FlameGraph は Perl スクリプトの集まりなので、gitperl を入れて clone するだけで使えます。プロファイル対象は、関数名がフレームグラフに出るよう -g -fno-omit-frame-pointer 付きでビルドします。




ubuntu@linuxlab: ~
$ sudo apt install -y git perl gcc
$ git clone https://github.com/brendangregg/FlameGraph.git
$ ls FlameGraph/
flamegraph.pl stackcollapse-perf.pl jmaps …
# フレームポインタを残してビルド(関数名を解決するため)
$ gcc -O0 -g -fno-omit-frame-pointer -o work work.c -lm

なぜ -fno-omit-frame-pointer が要るのか

コンパイラは最適化のためにフレームポインタ(呼び出し元をたどるための目印)を省略することがあります。これが省略されていると perf がスタックを正しくたどれず、フレームグラフが [unknown] だらけになります。Rust や Go、C/C++ のリリースビルドでは -fno-omit-frame-pointer を付けてビルドし直すと解決することが多いです。

②perf record でCPUをサンプリングする

-F 99 は1秒間に99回サンプリングする指定です。あえて100の倍数を避けることで サンプリングが他の周期的な処理(タイマー割り込みなど)と歩調を合わせてしまう「共鳴」を防ぐテクニックです(この話、知ったときは地味に面白かったです)。




ubuntu@linuxlab: ~
$ sudo perf record -e cpu-clock -g –call-graph fp -F 99 -o perf.data — ./work
[ perf record: Woken up 2 times to write data ]
[ perf record: Captured and wrote 0.253 MB perf.data (2344 samples) ]
  • -g --call-graph fp:コールスタック(呼び出し元)をフレームポインタ方式で記録する(フレームグラフに必須)
  • -F 99:サンプリング周波数 99Hz
  • -o perf.data:出力ファイル名
  • -- ./work:プロファイル対象のコマンド

今回は 2,344サンプルが取得できました。負荷が軽い、あるいは実行時間が短いとサンプル数が足りずスカスカな図になるので、その場合は対象プログラムを長めに回すのがコツです。

③スタックトレースをテキスト変換して畳み込む




ubuntu@linuxlab: ~
$ sudo perf script -i perf.data > perf.stacks
$ wc -l perf.stacks
18833 perf.stacks
$ ./FlameGraph/stackcollapse-perf.pl perf.stacks > perf.folded
$ wc -l perf.folded
13 perf.folded

18,833行あった生スタックが、stackcollapse-perf.pl によって同一の呼び出しパスにまとめられ、たった13行のfolded形式に集約されました。今回のプログラムは関数の種類が少ないので極端に減っていますが、この「重複する呼び出しパスを数える」という処理こそがフレームグラフの本質です。

④SVG を生成してブラウザで確認する




ubuntu@linuxlab: ~
$ ./FlameGraph/flamegraph.pl perf.folded > flamegraph.svg
$ wc -c flamegraph.svg
20676 flamegraph.svg
$ firefox flamegraph.svg # ブラウザで開く

約20KBのSVGファイルが生成されました。サーバー上で作った場合は scp user@vps:~/flamegraph.svg . のようにローカルに転送し、ブラウザで開けばOKです。SVGなので拡大しても潰れません。

perf record → script → stackcollapse → flamegraph.pl 実行ログ(2,344サンプル)実測
perf record → script → stackcollapse → flamegraph.pl 実行ログ(2,344サンプル)実測

Flame Graph の見方

実際に生成したSVGをブラウザで表示すると、次のように見えます。今回プロファイルした work のフレームグラフです。

実際に perf で生成した Flame Graph SVG(Playwright 実撮影)
実際に perf で生成した Flame Graph SVG(Playwright 実撮影)

下から work → _start → __libc_start_main → main と土台のスタックが積まれ、その上に 最も幅広い compute_hash → hash_mix のブロックが乗っているのが見えます。右端には細い compute_integer → heavy_modulo が、さらにその外側にごく細い平方根計算(heavy_sqrt から sin / sqrt)が見えます。

フレームグラフの読み方の基本は次の通りです。

  • 横の幅:その関数がCPUを占有した割合(幅が広いほどホットスポット)
  • 縦の高さ:コールスタックの深さ(上の関数が実際に走っている関数)
  • :基本はランダムな暖色で、色自体に意味はありません(視認性のため)
  • クリック:フレームをクリックするとそこを基準にズームし、左上に「Reset Zoom」が出る

試しに一番ホットな hash_mix をクリックしてズームすると、その関数が全幅に拡大され、内訳を細かく確認できます。

hash_mix フレームをクリックでズームした状態(Playwright 実撮影)
hash_mix フレームをクリックでズームした状態(Playwright 実撮影)

フレームグラフで「ボトルネック」を見つける方法

  • 幅の広いフレームを上から探す。今回は hash_mix が全体の89.12%を占めており、ここを速くすればプログラム全体が劇的に速くなると分かる
  • [unknown] が並ぶのはデバッグシンボルやフレームポインタが無い部分(-g -fno-omit-frame-pointer や debuginfo パッケージで改善できることがある)
  • カーネル関数が幅広い場合は「システムコールやI/Oが多い」サイン。perf stat の sys 時間と合わせて見ると判断しやすい

Ubuntu バージョン別 perf 対応比較

Ubuntu 22.04 LTS と 24.04 LTS で perf のバージョンがどう違うのか、両方のコンテナで実際に linux-tools-generic をインストールして perf --version を確認しました。

Ubuntu 22.04 vs 24.04 perf ツール実測比較
Ubuntu 22.04 vs 24.04 perf ツール実測比較
項目 Ubuntu 22.04 LTS (Jammy) Ubuntu 24.04 LTS (Noble)
OSバージョン 22.04.5 LTS 24.04.4 LTS
linux-tools-common 候補 5.15.0-181.191 6.8.0-124.124
perf バイナリ(実測) perf version 5.15.199 perf version 6.8.12
インストールコマンド linux-tools-generic linux-tools-generic
推奨度 / サポート期限 継続利用可(〜2027年4月) ★推奨(〜2029年4月 LTS)

どちらも linux-tools-generic でインストールでき、フレームグラフの作り方は同じです。新規に環境を作るなら、perf 6.8.12 が入りサポート期限も長い Ubuntu 24.04 LTS を選ぶのが安心です。

よくあるエラーと解決策

①「perf not found for kernel X.X.X」が出る




ubuntu@linuxlab: ~
WARNING: perf not found for kernel 5.10.76
You may need to install:
linux-tools-5.10.76-linuxkit

これは perf ラッパーが「動作中のカーネル用の perf」を探すのに、それが見つからないときに出ます。Mac の Docker Desktop や Rancher Desktop のコンテナでは、ホストが linuxkit という仮想化用カーネルで動いているため、コンテナに入れた linux-tools-generic(汎用カーネル 6.8.0 用)とバージョンが噛み合わないのが原因です。

解決策:実体のバイナリを直接呼べば動きます。今回の検証もこの方法で perf statperf record を実行しました。




ubuntu@linuxlab: ~
$ ls /usr/lib/linux-tools/
6.8.0-124-generic
$ /usr/lib/linux-tools/6.8.0-124-generic/perf –version
perf version 6.8.12

VPS(Ubuntu 24.04 の実機)なら動作中のカーネルと perf のバージョンが一致するので、このような小細工なしに perf がそのまま使えます。本格的にプロファイリングするなら実機VPSが圧倒的に楽です。

②「Permission denied」や空のデータになる

一般ユーザーでカーネルイベントを取得できるかは /proc/sys/kernel/perf_event_paranoid の値で決まります。値が高いと perf record がはじかれます。




ubuntu@linuxlab: ~
$ cat /proc/sys/kernel/perf_event_paranoid
3
$ sudo sysctl -w kernel.perf_event_paranoid=1
kernel.perf_event_paranoid = 1

本番環境での注意

perf_event_paranoid=1 は一般ユーザーがカーネルイベントを取得できる設定です。デフォルト値は環境によって異なります(よく見るのは 2〜4。本検証のコンテナでは 1 でした)。セキュリティ要件が厳しい本番サーバーでは、プロファイリング後に元の値へ戻すことをおすすめします。

③フレームグラフが [unknown] だらけになる

-g でコールスタックを記録するには、対象プログラムがフレームポインタを保持してビルドされている必要があります。-fno-omit-frame-pointer を付けてビルドし直すか、対象のライブラリの debuginfo パッケージを入れるとシンボルが解決され、関数名がきちんと表示されるようになります。

まずは perf record -e cpu-clock -F 99 -o perf.data -- ./program-g なし)で動くか試し、その後 -g --call-graph fp を足してスタックが取れるか確認するのが安全です。

まとめ

perf と FlameGraph を使えば、Ubuntu上のCPUボトルネックを「見て」特定できます。重要なポイントを振り返ります。

  • linux-tools-generic で perf 6.8.12 が入る(Ubuntu 24.04 で実測)
  • まず perf stat で全体像(cpu-clock 2,367ms など)を把握してから perf record で詳細を取るのが定石
  • フレームグラフは perf record → perf script → stackcollapse-perf.pl → flamegraph.pl の4ステップ
  • 今回は 2,344サンプル → 18,833行 → folded 13行 → 約20KBのSVGになり、hash_mix が89.12%を占めると一目で分かった(2026-06-14 実測)
  • コンテナでカーネルが噛み合わないときは /usr/lib/linux-tools/<ver>/perf を直接呼ぶ。VPS実機ならそのまま動く

本格的にサーバーのパフォーマンスを分析・改善したいなら、カーネルイベントに自由にアクセスできる自分のVPS環境が便利です。コンテナのような制約がなく、perf が本来の力を発揮できます。

— VPSの比較・選び方ガイドもあわせてご覧ください。

コメント

タイトルとURLをコピーしました