Linux Kernel Tracing and Profiling
Linux カーネル トレーシング & プロファイリング 包括的ガイド
ftrace, BPF, perf, およびその先へ
著者: AI生成 技術リファレンス
日付: 2026年4月
バージョン: 1.0
対象読者: シニアLinuxエンジニア、SRE、カーネル開発者
目次
- はじめに: Linuxカーネルトレーシング & プロファイリング
- Linuxトレーシングの全体像
- ftrace: ファンクショントレーサー
- trace-cmdとKernelShark
- Tracepoints: 静的計装
- KprobesとKretprobes: 動的計装
- Uprobes: ユーザー空間プローブ
- perf: Linuxパフォーマンスカウンター
- perf 高度なサブシステムツール
- フレームグラフ: Brendan Gregg方法論
- eBPFアーキテクチャ
- BPFプログラムタイプ
- bpftrace: 言語とワンライナー
- BCC: BPFコンパイラコレクション
- libbpfとCO-RE
- SystemTap概要
- /sys/kernel/debug/tracing/ インターフェース
- 実践的デバッグシナリオ
- ツールリファレンスと比較
- 付録: カーネル設定とセットアップ
1. はじめに: Linuxカーネルトレーシング & プロファイリング
1.1 カーネルトレーシングの重要性
現代の本番システムは、オペレーティングシステムカーネルへの深いオブザーバビリティを要求しています。マイクロサービスのレイテンシスパイクをデバッグする場合でも、長時間実行されるデーモンのメモリリークを追跡する場合でも、データベースワークロードのI/Oボトルネックを理解する場合でも、カーネルレベルのトレーシングとプロファイリングは、アプリケーションレベルの監視では提供できない根本的な真実を提供します。
Linuxカーネルは、あらゆるオペレーティングシステムの中で最も豊富なトレーシングエコシステムの一つを提供しています。過去20年以上にわたり、コミュニティはトレーシング技術の階層的なインフラストラクチャを構築し洗練してきました。カーネルソースにコンパイルされた静的トレースポイントから、カーネルおよびユーザー空間コードの任意のアドレスに挿入できる動的プローブ、そして安全でプログラム可能なカーネル内分析を可能にする革命的なeBPF仮想マシンまで、多岐にわたります。
1.2 Linuxトレーシングの進化
Linuxトレーシングの歴史は、20年以上にわたるイノベーションに及びます:
| 年 | 技術 | 重要性 |
|---|---|---|
| 2004 | Kprobes統合 | 動的カーネルプローブ機能 |
| 2005 | SystemTap開始 | スクリプト可能なカーネル計装 |
| 2008 | ftrace統合 (2.6.27) | 組み込みファンクショントレースフレームワーク |
| 2009 | perf_events統合 (2.6.31) | ハードウェアパフォーマンスカウンターサブシステム |
| 2009 | tracepoints | 静的カーネル計装ポイント |
| 2012 | Uprobes統合 (3.5) | ユーザー空間動的プローブ |
| 2014 | eBPF導入 (3.18) | 拡張Berkeley Packet Filter |
| 2015 | BCCプロジェクト開始 | PythonベースBPFツールセット |
| 2016 | BPF maps、追加プログラムタイプ | BPF向け豊富なデータ構造 |
| 2018 | bpftraceリリース | 高レベルBPFトレーシング言語 |
| 2019 | libbpf + CO-RE | ポータブルBPFプログラム |
| 2020 | BPF LSM, ringbuf | セキュリティモジュール、効率的バッファ |
| 2021-2025 | BPF arena, kfuncs, tokens | 継続的な急速な進化 |
1.3 適切なツールの選択
ツールの豊富さは圧倒的になりえます。以下は判断フレームワークです:
- クイックなワンライナー調査 →
bpftrace - 詳細なCPUプロファイリング →
perf record+ フレームグラフ - 関数レベルのカーネルトレーシング →
ftrace/trace-cmd - 複雑なカスタム分析 → BCCツールまたはlibbpfによるカスタムeBPF
- ハードウェアカウンター分析 →
perf stat - リアルタイムモニタリング →
perf top,bpftrace - ネットワークパケット処理 → XDP (eBPF)
- レガシーシステム(古いカーネル) → SystemTap
1.4 前提条件
このガイド全体を通して、以下を前提とします:
- Linux カーネル 5.x以降(eBPFの完全サポートには6.x推奨)
- root権限または適切なケーパビリティ(
CAP_SYS_ADMIN,CAP_BPF,CAP_PERFMON) - カーネル設定で
CONFIG_FTRACE=y,CONFIG_BPF_SYSCALL=y,CONFIG_KPROBES=y - 標準開発ツール:
gcc,make,clang,llvm
# カーネルバージョンと主要設定の確認
uname -r
# 6.8.0-generic
# トレーシングサポートの確認
cat /boot/config-$(uname -r) | grep -E "CONFIG_FTRACE|CONFIG_BPF|CONFIG_KPROBES|CONFIG_UPROBES"
# CONFIG_FTRACE=y
# CONFIG_BPF_SYSCALL=y
# CONFIG_BPF_JIT=y
# CONFIG_KPROBES=y
# CONFIG_UPROBES=y
# BPFファイルシステムの確認
mount | grep bpf
# bpf on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700)
2. Linuxトレーシングの全体像
2.1 アーキテクチャ概要
Linuxトレーシングサブシステムは、3つの主要な層を持つ階層的アーキテクチャです:
┌─────────────────────────────────────────────────────────────────┐
│ ユーザー空間ツール │
│ perf │ trace-cmd │ bpftrace │ BCC │ SystemTap │ ... │
├─────────────────────────────────────────────────────────────────┤
│ フロントエンド / インターフェース │
│ perf_event_open() │ tracefs │ bpf() │ debugfs │
├─────────────────────────────────────────────────────────────────┤
│ カーネルフレームワーク │
│ perf_events │ ftrace │ eBPF VM │ relay │ ring_buffer │
├─────────────────────────────────────────────────────────────────┤
│ 計装ソース │
│ Tracepoints │ Kprobes │ Uprobes │ HW PMCs │ SW events│
├─────────────────────────────────────────────────────────────────┤
│ カーネル / ハードウェア │
│ スケジューラ │ メモリ │ I/O │ ネットワーク │ CPU PMU │
└─────────────────────────────────────────────────────────────────┘
2.2 データソース
ハードウェアパフォーマンスモニタリングカウンター (PMCs):
- CPUサイクル、リタイアされた命令数、キャッシュヒット/ミス
- 分岐予測統計
- TLBミス、メモリ帯域幅
perf_event_open()システムコール経由でアクセス
ソフトウェアイベント:
- コンテキストスイッチ、ページフォールト、CPU移行
- スケジューラ、メモリ、I/Oサブシステム向けのカーネル生成イベント
Tracepoints:
- カーネルにコンパイルされた静的計装ポイント
- カーネルバージョン間で安定したAPI(概ね)
- 戦略的なポイントに配置: スケジューラ、メモリアロケータ、ファイルシステム、ネットワーク
動的プローブ:
- Kprobes: 任意のカーネル関数または命令にプローブ
- Kretprobes: 関数リターンにプローブ
- Uprobes: ユーザー空間の関数エントリ/リターンにプローブ
- カーネルやアプリケーションの再コンパイル不要
2.3 データ転送メカニズム
Ring Buffer (ftrace) perf_event ring buffer BPF ring buffer
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ CPU単位 │ │ CPU単位 │ │ 単一共有 │
│ ロックフリー │ │ またはタスク │ │ バッファ │
│ リングバッファ │ │ mmap対応 │ │ (bpf_ringbuf)│
│ │ │ リングバッファ │ │ │
│ tracefs経由 │ │ poll/mmap │ │ poll/mmap │
│ spliceで読出 │ │ で読出 │ │ epollで読出 │
└──────────────┘ └──────────────┘ └──────────────┘
2.4 オーバーヘッドに関する考慮事項
トレーシングは常にいくらかのオーバーヘッドを追加します。コストモデルの理解は本番使用において重要です:
| メカニズム | 典型的なオーバーヘッド | 備考 |
|---|---|---|
| Tracepoint (無効) | 約0 (NOP) | 非アクティブ時はNOPにパッチ |
| Tracepoint (有効) | 約100-300ns | イベント単位のコスト |
| Kprobe | 約200-500ns | 動的パッチ、ブレークポイントまたはJMP |
| Kretprobe | 約500-1000ns | トランポリンオーバーヘッド |
| Uprobe | 約1-5us | ユーザー・カーネル遷移を含む |
| ftrace function | 約100-300ns | コンパイラ挿入コールサイト |
| eBPFプログラム | 約50-500ns | プログラムの複雑さに依存 |
| perf PMCサンプリング | 約1-5% CPU | サンプリング周波数に依存 |
本番トレーシングのガイドライン:
- 利用可能な場合はkprobesよりtracepoints優先(より安定、低オーバーヘッド)
- 可能な場合はすべてのイベントのトレースではなくサンプリング(perf)を使用
- ユーザー空間ではなくカーネル内(eBPF)でイベントをフィルタリング
- メモリ圧迫を避けるためバッファサイズを制限
- 本番環境にデプロイする前にステージングでオーバーヘッドをテスト
3. ftrace: ファンクショントレーサー
3.1 ftraceの紹介
ftrace(ファンクショントレーサー)は、Linuxカーネルに組み込まれた主要なトレーシングフレームワークです。Steven Rostedtによって作成され、通常 /sys/kernel/debug/tracing/ または /sys/kernel/tracing/ にマウントされるtracesfsファイルシステムを通じて、豊富なトレーシング機能を提供します。
ftraceは -mfentry または -pg コンパイラフラグを活用して動作します。これにより、すべてのカーネル関数の先頭に mcount() (または __fentry__()) への呼び出しが挿入されます。ftraceがアクティブでない場合、これらのコールサイトはNOP(無操作命令)にパッチされ、無視できるほどのオーバーヘッドになります。
3.2 tracefs ファイルシステム
# tracefs のマウント(通常は自動マウント)
mount -t tracefs tracefs /sys/kernel/debug/tracing
# tracefs の内容を一覧表示
ls /sys/kernel/debug/tracing/
# available_events current_tracer instances/
# available_filter_functions events/ kprobe_events
# available_tracers free_buffer max_graph_depth
# buffer_percent function_profile_enabled per_cpu/
# buffer_size_kb hwlat_detector/ printk_formats
# buffer_total_size_kb instances/ README
# ...
tracefs の主要ファイル:
| ファイル | 目的 |
|---|---|
available_tracers | コンパイル済みトレーサー一覧 |
current_tracer | 現在アクティブなトレーサー |
trace | 人間が読めるトレース出力 |
trace_pipe | ストリーミングトレース出力(消費読み取り) |
tracing_on | トレーシングの有効/無効 (1/0) |
set_ftrace_filter | トレース対象関数のフィルター |
set_ftrace_notrace | トレースから除外する関数 |
set_ftrace_pid | 特定PIDのみトレース |
buffer_size_kb | CPU単位のバッファサイズ |
events/ | 利用可能なtracepointイベントのディレクトリ |
trace_options | トレース出力オプションの切替 |
trace_stat/ | 統計トレーシングデータ |
kprobe_events | kprobeベースのイベント定義 |
uprobe_events | uprobeベースのイベント定義 |
3.3 ファンクショントレーサー
ファンクショントレーサーは、すべてのカーネル関数呼び出しを記録し、カーネル実行フローの詳細なビューを提供します。
# 利用可能なトレーサーの確認
cat /sys/kernel/debug/tracing/available_tracers
# hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
# ファンクショントレーサーの有効化
echo function > /sys/kernel/debug/tracing/current_tracer
# トレーシング開始
echo 1 > /sys/kernel/debug/tracing/tracing_on
# アクティビティが発生するのを待ち、停止
sleep 1
echo 0 > /sys/kernel/debug/tracing/tracing_on
# トレースの表示
head -30 /sys/kernel/debug/tracing/trace
サンプル出力:
# tracer: function
#
# entries-in-buffer/entries-written: 205432/1847352 #P:8
#
# _-----=> irqs-off/BH-disabled
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / _-=> migrate-disable
# |||| / delay
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
# | | | ||||| | |
<idle>-0 [003] d..1. 1234.567890: tick_nohz_idle_exit <-do_idle
<idle>-0 [003] d..1. 1234.567891: ktime_get <-tick_nohz_idle_exit
<idle>-0 [003] d..1. 1234.567892: tick_nohz_idle_got_tick <-tick_nohz_idle_exit
3.4 関数のフィルタリング
すべてのカーネル関数をトレースすると膨大な量のデータが生成されます。フィルターを使用して関連する関数に焦点を当てましょう:
# 関数名でフィルター(globパターンをサポート)
echo 'schedule*' > /sys/kernel/debug/tracing/set_ftrace_filter
# 複数フィルター(>> で追加)
echo 'vfs_*' >> /sys/kernel/debug/tracing/set_ftrace_filter
echo 'ext4_*' >> /sys/kernel/debug/tracing/set_ftrace_filter
# 現在のフィルターを表示
cat /sys/kernel/debug/tracing/set_ftrace_filter
# 特定の関数を除外
echo '!schedule_timeout' > /sys/kernel/debug/tracing/set_ftrace_notrace
# モジュールでフィルター
echo ':mod:ext4' > /sys/kernel/debug/tracing/set_ftrace_filter
# すべてのフィルターをクリア
echo > /sys/kernel/debug/tracing/set_ftrace_filter
# 特定のPIDのみトレース
echo $$ > /sys/kernel/debug/tracing/set_ftrace_pid
3.5 ファンクショングラフトレーサー
function_graphトレーサーは関数の入口と出口を表示し、呼び出し階層と実行時間を示します:
# function_graph トレーサーの有効化
echo function_graph > /sys/kernel/debug/tracing/current_tracer
# 出力が圧倒的にならないようにグラフの深さを制限
echo 5 > /sys/kernel/debug/tracing/max_graph_depth
# 特定の関数にフィルター
echo 'do_sys_openat2' > /sys/kernel/debug/tracing/set_graph_function
# トレーシングを有効化してファイルオープンをトリガー
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /dev/null
echo 0 > /sys/kernel/debug/tracing/tracing_on
# グラフの表示
cat /sys/kernel/debug/tracing/trace
サンプル出力:
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
3) | do_sys_openat2() {
3) | getname_flags() {
3) | kmem_cache_alloc() {
3) 0.156 us | should_failslab();
3) 0.651 us | }
3) | strncpy_from_user() {
3) 0.346 us | _copy_from_user();
3) 0.752 us | }
3) 2.148 us | }
3) | path_openat() {
3) | alloc_empty_file() {
3) 0.187 us | kmem_cache_alloc();
3) 0.128 us | security_file_alloc();
3) 0.736 us | }
3) | path_init() {
3) 0.231 us | nd_jump_link();
3) 0.596 us | }
3) | link_path_walk.part.0() {
3) 0.142 us | inode_permission();
3) 0.567 us | }
3) | do_open() {
3) 0.198 us | vfs_open();
3) 0.456 us | }
3) 5.234 us | }
3) 8.912 us | }
3.6 イベントトレーシング
ftraceはtracepointベースのイベントトレーシングをサポートし、特定のカーネルイベントをキャプチャできます:
# 利用可能なすべてのイベントを一覧表示
cat /sys/kernel/debug/tracing/available_events | head -20
# イベントカテゴリーの一覧表示
ls /sys/kernel/debug/tracing/events/
# block/ ext4/ irq/ kmem/ mmc/ net/ power/ raw_syscalls/
# sched/ signal/ skb/ sock/ syscalls/ task/ timer/ ...
# 特定のイベントを有効化
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
# カテゴリー内のすべてのイベントを有効化
echo 1 > /sys/kernel/debug/tracing/events/sched/enable
# イベントフォーマットの表示
cat /sys/kernel/debug/tracing/events/sched/sched_switch/format
3.7 イベントフィルタリング
# フィールド値でイベントをフィルター
echo 'prev_comm == "nginx"' > /sys/kernel/debug/tracing/events/sched/sched_switch/filter
echo 'next_pid > 0' > /sys/kernel/debug/tracing/events/sched/sched_wakeup/filter
# 論理演算子による複合フィルター
echo 'prev_comm == "nginx" || next_comm == "nginx"' > \
/sys/kernel/debug/tracing/events/sched/sched_switch/filter
# 数値比較
echo 'bytes_req > 4096' > /sys/kernel/debug/tracing/events/kmem/kmalloc/filter
# フィルターのクリア
echo 0 > /sys/kernel/debug/tracing/events/sched/sched_switch/filter
3.8 トレーストリガー
ftraceはイベント発生時にアクションを実行するトリガーをサポートしています:
# スタックトレーストリガー: イベント発火時にコールスタックをキャプチャ
echo 'stacktrace' > /sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
# スナップショットトリガー: トレースバッファのスナップショットを取得
echo 'snapshot' > /sys/kernel/debug/tracing/events/sched/sched_switch/trigger
# ヒストグラムトリガー: イベントデータのヒストグラムを作成
echo 'hist:key=bytes_req:val=hitcount:sort=hitcount.descending' > \
/sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
# ヒストグラムの表示
cat /sys/kernel/debug/tracing/events/kmem/kmalloc/hist
# 条件付きトリガー
echo 'stacktrace if bytes_req > 65536' > \
/sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
# 他のイベントの有効化/無効化
echo 'enable_event:sched:sched_switch' > \
/sys/kernel/debug/tracing/events/sched/sched_wakeup/trigger
# トリガーの削除
echo '!stacktrace' > /sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
3.9 ヒストグラムトリガー (hist triggers)
ヒストグラムトリガーは、強力なカーネル内集計を提供します:
# スケジューラのwakeup-to-switchレイテンシヒストグラム
echo 'hist:key=next_pid:val=hitcount:sort=hitcount.descending' > \
/sys/kernel/debug/tracing/events/sched/sched_switch/trigger
# 合成イベントによるレイテンシ測定
# ステップ1: 合成イベントの定義
echo 'wakeup_latency u64 lat; pid_t pid; char comm[16]' > \
/sys/kernel/debug/tracing/synthetic_events
# ステップ2: sched_wakingにhist triggerを作成してタイムスタンプを保存
echo 'hist:keys=pid:ts0=common_timestamp.usecs' > \
/sys/kernel/debug/tracing/events/sched/sched_waking/trigger
# ステップ3: sched_switchにhist triggerを作成してレイテンシを計算
echo 'hist:keys=next_pid:lat=common_timestamp.usecs-$ts0:onmatch(sched.sched_waking).trace(wakeup_latency,$lat,next_pid,next_comm)' > \
/sys/kernel/debug/tracing/events/sched/sched_switch/trigger
3.10 ftraceインスタンス
ftraceは複数の独立したトレーシングインスタンスをサポートします:
# 新しいインスタンスの作成
mkdir /sys/kernel/debug/tracing/instances/my_trace
# 独立して設定
echo function > /sys/kernel/debug/tracing/instances/my_trace/current_tracer
echo 'tcp_*' > /sys/kernel/debug/tracing/instances/my_trace/set_ftrace_filter
# 各インスタンスは独自のバッファを持つ
echo 8192 > /sys/kernel/debug/tracing/instances/my_trace/buffer_size_kb
# このインスタンスでイベントを有効化
echo 1 > /sys/kernel/debug/tracing/instances/my_trace/events/net/net_dev_xmit/enable
# このインスタンスからトレースを読み出す
cat /sys/kernel/debug/tracing/instances/my_trace/trace
# インスタンスの削除(先にトレーシングを無効化)
echo nop > /sys/kernel/debug/tracing/instances/my_trace/current_tracer
rmdir /sys/kernel/debug/tracing/instances/my_trace
3.11 実践的なftraceスクリプト
スクリプト: 低速ファイルシステム操作のトレース
#!/bin/bash
# trace_slow_fs.sh - 1ms以上のファイルシステム操作をトレース
TRACEFS=/sys/kernel/debug/tracing
THRESHOLD_US=1000
# リセット
echo 0 > $TRACEFS/tracing_on
echo nop > $TRACEFS/current_tracer
echo > $TRACEFS/trace
# VFS関数用のfunction_graphトレーサーを設定
echo function_graph > $TRACEFS/current_tracer
echo 10 > $TRACEFS/max_graph_depth
# ファイルシステム関数にフィルター
echo 'vfs_read' > $TRACEFS/set_graph_function
echo 'vfs_write' >> $TRACEFS/set_graph_function
echo 'vfs_open' >> $TRACEFS/set_graph_function
echo 'vfs_fsync' >> $TRACEFS/set_graph_function
# トレーシング閾値を設定(期間が閾値を超えた場合のみ記録)
echo $THRESHOLD_US > $TRACEFS/tracing_thresh
# トレーシング開始
echo 1 > $TRACEFS/tracing_on
echo "トレーシング開始。Ctrl+Cで停止..."
trap "echo 0 > $TRACEFS/tracing_on; echo 'トレースをtrace_slow_fs.outに保存'; \
cp $TRACEFS/trace trace_slow_fs.out; \
echo nop > $TRACEFS/current_tracer" INT
# ユーザーの割り込みを待つ
while true; do sleep 1; done
4. trace-cmdとKernelShark
4.1 trace-cmdの紹介
trace-cmdは、Steven Rostedtが作成した、ftraceのユーザーフレンドリーなコマンドラインフロントエンドです。クリーンなコマンドラインインターフェースを提供することで、tracefsファイルの設定という複雑なプロセスを簡素化します。
# インストール
# Debian/Ubuntu
sudo apt-get install trace-cmd
# RHEL/CentOS/Fedora
sudo dnf install trace-cmd
# ソースからビルド
git clone https://git.kernel.org/pub/scm/utils/trace-cmd/trace-cmd.git
cd trace-cmd
make
sudo make install
4.2 基本的なtrace-cmdの使い方
# 5秒間すべてのイベントを記録
sudo trace-cmd record -e all sleep 5
# 特定のイベントを記録
sudo trace-cmd record -e sched_switch -e sched_wakeup sleep 5
# フィルター付きの関数トレーシングを記録
sudo trace-cmd record -p function -l 'tcp_*' sleep 5
# 深さ制限付きのfunction_graphを記録
sudo trace-cmd record -p function_graph --max-graph-depth 4 -g do_sys_openat2 sleep 2
# 特定のバッファサイズで記録
sudo trace-cmd record -b 16384 -e sched sleep 5
# 特定のPIDを記録
sudo trace-cmd record -P 1234 -e sched -e irq sleep 10
# スタックトレース付きで記録
sudo trace-cmd record -e kmem:kmalloc -T sleep 5
4.3 トレースの分析
# トレースデータのレポート(人間が読める形式)
trace-cmd report
# タイミング情報付きレポート
trace-cmd report --ts-diff
# レポート出力のフィルタリング
trace-cmd report -F 'sched_switch: prev_comm == "nginx"'
# トレース統計の表示
trace-cmd report --stat
# 記録されたイベントの一覧
trace-cmd report --events
4.4 trace-cmdプロファイルとスナップショット
# システムアクティビティのプロファイルを作成
sudo trace-cmd profile sleep 10
# リアルタイムでトレース出力をストリーム
sudo trace-cmd stream -e sched_switch
# スナップショットを取得
sudo trace-cmd snapshot
# 対話的に記録を開始/停止
sudo trace-cmd start -e sched -p function -l 'schedule*'
# ... 何かを実行 ...
sudo trace-cmd stop
sudo trace-cmd extract # trace.datに保存
sudo trace-cmd reset # クリーンアップ
4.5 KernelShark
KernelSharkは、trace-cmdデータ分析のグラフィカルフロントエンドです。カーネルイベントのインタラクティブなタイムライン可視化を提供します。
# インストール
sudo apt-get install kernelshark
# トレースファイルを開く
kernelshark trace.dat
# 記録してすぐにKernelSharkで開く
sudo trace-cmd record -e sched sleep 5
kernelshark trace.dat
KernelSharkの機能:
- CPU間のイベントを表示するインタラクティブなタイムライン
- タスクベースのフィルタリング
- イベントベースのフィルタリング
- トレースデータのズームとスクロール
- カスタム可視化のためのプラグインアーキテクチャ
- 同期されたグラフとリストビュー
- CPUとタスクのプロット
5. Tracepoints: 静的計装
5.1 Tracepointsの理解
Tracepointsは、カーネルソースコードに埋め込まれた静的計装ポイントです。動的プローブの脆弱性なしに、特定のカーネルイベントをトレースするための安定したインターフェースを提供します。
主な特徴:
- カーネルにコンパイル - 開発者が選択した特定の場所に配置
- 無効時のオーバーヘッドはほぼゼロ (NOP命令パッチング)
- 安定したAPI - tracepointのフォーマットはカーネルバージョン間で維持
- 型安全 - 引数はコンパイル時に定義
- 探索可能 - tracefs/sysfsに一覧表示
5.2 利用可能なTracepointsの一覧表示
# すべてのtracepointsを一覧表示
cat /sys/kernel/debug/tracing/available_events | wc -l
# 通常1500以上のtracepoints
# カテゴリー別に一覧表示
cat /sys/kernel/debug/tracing/available_events | cut -d: -f1 | sort -u
# block
# cgroup
# ext4
# irq
# kmem
# net
# sched
# syscalls
# task
# tcp
# timer
# vmscan
# workqueue
# writeback
# ...
# カテゴリー内のtracepointsを一覧表示
ls /sys/kernel/debug/tracing/events/sched/
5.3 カーネルモジュールでカスタムTracepointsを定義
/* my_tracepoint.h */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM my_module
#if !defined(_MY_TRACEPOINT_H) || defined(TRACE_HEADER_MULTI_READ)
#define _MY_TRACEPOINT_H
#include <linux/tracepoint.h>
TRACE_EVENT(my_module_request,
/* プロトタイプ */
TP_PROTO(const char *name, int size, unsigned long flags),
/* 引数 */
TP_ARGS(name, size, flags),
/* 構造体定義 */
TP_STRUCT__entry(
__string(name, name)
__field(int, size)
__field(unsigned long, flags)
),
/* 代入 */
TP_fast_assign(
__assign_str(name, name);
__entry->size = size;
__entry->flags = flags;
),
/* 出力フォーマット */
TP_printk("name=%s size=%d flags=0x%lx",
__get_str(name), __entry->size, __entry->flags)
);
#endif /* _MY_TRACEPOINT_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE my_tracepoint
#include <trace/define_trace.h>
6. KprobesとKretprobes: 動的計装
6.1 Kprobesの理解
Kprobes(カーネルプローブ)は、Linuxカーネルの動的計装を提供します。tracepointsとは異なり、kprobesはカーネルを再コンパイルすることなく、カーネル内の実質的に任意の命令に挿入できます。
Kprobesの動作原理:
- プローブポイントの元の命令を保存
- ブレークポイント命令に置き換え(x86では
int3) - ブレークポイントがヒットすると、プレハンドラーを実行
- 元の命令をシングルステップで実行
- ポストハンドラーを実行
- 通常の実行を再開
6.2 tracefs経由のKprobe
# kprobeイベントの追加
echo 'p:myprobe do_sys_openat2 dfd=%di:s32 filename=+0(%si):string flags=%dx:x32' > \
/sys/kernel/debug/tracing/kprobe_events
# kretprobeイベントの追加
echo 'r:myretprobe do_sys_openat2 retval=$retval:s64' >> \
/sys/kernel/debug/tracing/kprobe_events
# 定義済みkprobeイベントの表示
cat /sys/kernel/debug/tracing/kprobe_events
# イベントの有効化
echo 1 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
echo 1 > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable
# トレーシング開始
echo 1 > /sys/kernel/debug/tracing/tracing_on
# ファイルオープンを生成
ls /tmp
# トレースの表示
cat /sys/kernel/debug/tracing/trace
# 無効化とクリーンアップ
echo 0 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
echo 0 > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable
echo '-:myprobe' >> /sys/kernel/debug/tracing/kprobe_events
echo '-:myretprobe' >> /sys/kernel/debug/tracing/kprobe_events
6.3 perf経由のKprobes
# kprobeを動的に追加して記録
sudo perf probe --add 'do_sys_openat2 filename=+0(%si):string'
# プローブの一覧
sudo perf probe --list
# プローブを記録
sudo perf record -e probe:do_sys_openat2 -a sleep 10
# レポート
perf report
# プローブの削除
sudo perf probe --del probe:do_sys_openat2
6.4 eBPF (bpftrace) 経由のKprobes
# vfs_readのすべての呼び出しをサイズ引数付きでトレース
sudo bpftrace -e 'kprobe:vfs_read { printf("pid=%d comm=%s size=%d\n", pid, comm, arg2); }'
# リードサイズのヒストグラム
sudo bpftrace -e 'kretprobe:vfs_read /retval > 0/ { @bytes = hist(retval); }'
# プロセス別の関数呼び出しカウント
sudo bpftrace -e 'kprobe:tcp_sendmsg { @[comm] = count(); }'
# 関数内の滞在時間をトレース
sudo bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ {
@duration_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
7. Uprobes: ユーザー空間プローブ
7.1 Uprobesの理解
Uprobesは、カーネルからユーザー空間アプリケーションの動的計装を可能にします。kprobesと同様に動作しますが、ユーザー空間のバイナリを対象とします。
Uprobesの動作原理:
- カーネルが対象プロセスのテキストページを変更
- プローブポイントの命令をブレークポイントに置き換え
- ブレークポイントがヒットすると、カーネルがuprobeハンドラーを実行
- 元の命令をシングルステップで実行
- 実行が通常通り継続
7.2 bpftrace経由のUprobes
# すべてのmalloc呼び出しをサイズと戻り値付きでトレース
sudo bpftrace -e '
uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:malloc {
@start[tid] = arg0;
}
uretprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:malloc /@start[tid]/ {
printf("pid=%d comm=%s malloc(%d) = %p\n",
pid, comm, @start[tid], retval);
delete(@start[tid]);
}'
# 特定プロセスのmallocサイズのヒストグラム
sudo bpftrace -e '
uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "nginx"/ {
@size = hist(arg0);
}'
# pthread_mutex_lockの競合をトレース
sudo bpftrace -e '
uprobe:/usr/lib/x86_64-linux-gnu/libpthread.so.0:pthread_mutex_lock {
@start[tid] = nsecs;
}
uretprobe:/usr/lib/x86_64-linux-gnu/libpthread.so.0:pthread_mutex_lock /@start[tid]/ {
@lock_time_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
7.3 USDT (ユーザーレベル静的定義トレーシング)
USDTプローブは、カーネルtracepointsのユーザー空間版です。アプリケーションは、ツールがアタッチできる安定したプローブポイントを定義できます。
# アプリケーションにUSDTプローブがあるか確認
readelf -n /usr/sbin/mysqld | grep -A2 'stapsdt'
# または
tplist-bpfcc -l /usr/sbin/mysqld
# USDTプローブを持つ一般的なアプリケーション:
# - MySQL/MariaDB: query-start, query-done, connection-start
# - PostgreSQL: query-start, query-done, transaction-start
# - Python: function__entry, function__return, gc__start
# - Ruby: method__entry, method__return, gc__mark__begin
# - Node.js: http__server__request, gc__start
# - JVM: method__entry, gc__begin, thread__start
# USDT経由でPython関数呼び出しをトレース
sudo bpftrace -e '
usdt:/usr/bin/python3:function__entry {
printf("%s:%s:%d\n", str(arg0), str(arg1), arg2);
}'
8. perf: Linuxパフォーマンスカウンター
8.1 perfの紹介
perf(perf_eventsまたはperf toolsとも呼ばれる)は、Linuxの公式プロファイリングツールです。カーネルのパフォーマンスモニタリングサブシステムとインターフェースし、以下を提供します:
- ハードウェアパフォーマンスカウンターへのアクセス (CPU PMU)
- ソフトウェアイベントカウント
- Tracepointベースのイベント記録
- サンプリングベースのプロファイリング
- コールグラフ記録
- 統計分析
8.2 インストールとセットアップ
# Debian/Ubuntu
sudo apt-get install linux-tools-common linux-tools-$(uname -r)
# RHEL/CentOS/Fedora
sudo dnf install perf
# インストールの確認
perf version
# 利用可能なイベントの確認
perf list | head -40
8.3 perf stat: イベントのカウント
perf statは個々のサンプルを記録せずに、プログラム実行中のイベントをカウントします。
# 基本的なCPU統計
sudo perf stat ls -la /tmp
# Performance counter stats for 'ls -la /tmp':
#
# 1.23 msec task-clock # 0.567 CPUs utilized
# 2 context-switches # 1.626 K/sec
# 0 cpu-migrations # 0.000 K/sec
# 112 page-faults # 91.057 K/sec
# 3456789 cycles # 2.811 GHz
# 2345678 instructions # 0.68 insn per cycle
# 456789 branches # 371.373 M/sec
# 12345 branch-misses # 2.70% of all branches
# 詳細統計
sudo perf stat -d ls -la /tmp
# L1-dcache, LLC-loads, LLC-load-misses等が追加
# 非常に詳細な統計
sudo perf stat -ddd ls -la /tmp
# 特定のイベント
sudo perf stat -e cycles,instructions,cache-references,cache-misses \
./my_program
# 実行中のプロセスをカウント
sudo perf stat -p 1234 sleep 10
# システム全体の統計
sudo perf stat -a sleep 10
# 統計的有意性のために繰り返し
sudo perf stat -r 10 ./my_program
# 平均、標準偏差、信頼区間を表示
# CPU単位の統計
sudo perf stat -a -A sleep 5
# インターバル出力 (1秒ごと)
sudo perf stat -I 1000 -e cycles,instructions -a
8.4 perf record: サンプリングプロファイラー
perf recordは定期的にプログラムカウンターをサンプリングし、統計的プロファイルを構築します。
# 基本的な記録 (CPUサイクルサンプリング)
sudo perf record ./my_program
# コールグラフ付き記録 (DWARF展開 - 最も正確)
sudo perf record -g --call-graph dwarf ./my_program
# コールグラフ付き記録 (フレームポインター - 低オーバーヘッド)
sudo perf record -g --call-graph fp ./my_program
# コールグラフ付き記録 (LBR - Intel Last Branch Record)
sudo perf record -g --call-graph lbr ./my_program
# 特定のサンプリング周波数 (タイマーとのロックステップを避けるため99Hz)
sudo perf record -F 99 -g -a sleep 30
# 特定のイベントを記録
sudo perf record -e cache-misses -g ./my_program
# Tracepointsを記録
sudo perf record -e sched:sched_switch -a sleep 10
# 実行中のプロセスを記録
sudo perf record -g -p $(pgrep nginx) sleep 30
# システム全体を記録
sudo perf record -g -a sleep 30
8.5 perf report: プロファイルの分析
# インタラクティブTUIレポート
perf report
# 特定のソートキーでレポート
perf report --sort comm,dso,symbol
# コールグラフ付きレポート(呼び出し元視点)
perf report --call-graph=callee
# コールグラフ付きレポート(呼び出し先視点)
perf report --call-graph=caller
# パーセンテージ閾値付きレポート
perf report --percent-limit 1.0
# stdout出力 (非インタラクティブ)
perf report --stdio
# 子オーバーヘッド (累積)
perf report --children
# フレームグラフフォーマットでエクスポート
perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg
8.6 perf annotate: ソースレベル分析
# 最もホットな関数にアノテーション
perf annotate -d my_program
# 特定の関数にアノテーション
perf annotate -s process_request
# ソースコード付きアノテーション(デバッグシンボルが必要)
perf annotate --source
# ファイルに出力
perf annotate --stdio > annotation.txt
8.7 perf top: リアルタイムプロファイリング
# リアルタイムのシステム全体プロファイリング
sudo perf top
# 特定プロセスのプロファイル
sudo perf top -p $(pgrep nginx)
# 特定イベントでプロファイル
sudo perf top -e cache-misses
# コールグラフ付き
sudo perf top -g
# 特定のソート順序
sudo perf top --sort comm,dso
8.8 perf trace: システムコールトレーシング
perf traceは低オーバーヘッドのstrace代替です:
# コマンドのすべてのsyscallをトレース
sudo perf trace ls /tmp
# 特定のsyscallをトレース
sudo perf trace -e open,read,write,close ls /tmp
# 実行中のプロセスをトレース
sudo perf trace -p $(pgrep nginx)
# サマリー付きシステム全体トレース
sudo perf trace -s sleep 10
# オーバーヘッドの比較: perf trace vs strace
time perf trace -o /dev/null ./my_program
time strace -o /dev/null ./my_program
# perf traceは通常straceより2-5倍高速
9. perf 高度なサブシステムツール
9.1 perf sched: スケジューラ分析
# スケジューライベントの記録
sudo perf sched record sleep 10
# レイテンシレポートの表示
sudo perf sched latency
# ---------------------------------------------------------------------------
# Task | Runtime ms | Switches | Avg delay ms | Max delay ms |
# ---------------------------------------------------------------------------
# nginx:1234 | 450.234 | 12345 | 0.045 | 12.345 |
# postgres:5678 | 234.567 | 8901 | 0.023 | 5.678 |
# スケジューリングマップの表示 (ASCIIアート)
sudo perf sched map
# 時間順のスケジューライベントの表示
sudo perf sched script
# スケジューリング統計の表示
sudo perf sched timehist
# ウェイクアップごとのレイテンシタイムラインを表示
# ウェイクアップ情報付きtimehist
sudo perf sched timehist -w
# 移行情報付きtimehist
sudo perf sched timehist -M
9.2 perf mem: メモリアクセス分析
# メモリアクセスイベントの記録(ハードウェアサポートが必要)
sudo perf mem record -a sleep 10
# メモリアクセスパターンのレポート
sudo perf mem report
# ロードのみを記録
sudo perf mem record --type load -a sleep 10
# ストアのみを記録
sudo perf mem record --type store -a sleep 10
# キャッシュラインの競合分析(フォルスシェアリング)
sudo perf c2c record -a sleep 10
sudo perf c2c report
# 競合情報付きの共有キャッシュラインを表示
9.3 perf lock: ロック競合分析
# ロックイベントの記録(CONFIG_LOCK_STATが必要)
sudo perf lock record sleep 10
# ロック競合サマリーの表示
sudo perf lock report
# Name acquired contended avg wait max wait total wait
# ---------------------------------------- -------- -------- ----------
# &rq->lock 12345 234 1.23us 45.67us 287.82us
# &mm->mmap_lock 8901 123 2.34us 67.89us 287.82us
# コールチェーン付きロック競合の表示
sudo perf lock contention -g
# ロック競合統計の表示
sudo perf lock contention -s
9.4 perf kmem: カーネルメモリ分析
# カーネルメモリアロケーションイベントの記録
sudo perf kmem record sleep 10
# SLABアロケータ統計
sudo perf kmem stat --slab
# ページアロケータ統計
sudo perf kmem stat --page
# 上位呼び出し元の表示
sudo perf kmem stat --caller
10. フレームグラフ: Brendan Gregg方法論
10.1 フレームグラフの紹介
フレームグラフは2011年にBrendan Greggによって発明され、スタックトレースデータの直感的な可視化を提供します:
- X軸: スタックトレースの母集団(幅 = 頻度/重み)
- Y軸: スタックの深さ(下 = on-CPU、上 = リーフ関数)
- 色: 差別化のためのランダムな暖色系(赤/黄/オレンジ)
- 各ボックス: スタックトレース内の関数
┌─────────────────────────────────────────────────────────────┐
│ func_leaf_a │
├──────────────────────┬──────────────────────────────────────┤
│ func_mid_a │ func_mid_b │
├──────────┬───────────┼──────────┬───────────────────────────┤
│ func_lo_a│func_lo_b │func_lo_c │ func_lo_d │
├──────────┴───────────┴──────────┴───────────────────────────┤
│ main │
└─────────────────────────────────────────────────────────────┘
10.2 CPUフレームグラフの生成
# FlameGraphツールのクローン
git clone https://github.com/brendangregg/FlameGraph.git
cd FlameGraph
# ステップ1: CPUプロファイルを記録
sudo perf record -F 99 -ag -- sleep 30
# ステップ2: フォールドスタックの生成
sudo perf script | ./stackcollapse-perf.pl > out.folded
# ステップ3: SVGフレームグラフの生成
./flamegraph.pl out.folded > cpu_flamegraph.svg
# ワンライナー
sudo perf record -F 99 -ag -- sleep 30 && \
sudo perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > cpu.svg
# タイトルとカスタムカラー付き
sudo perf script | ./stackcollapse-perf.pl | \
./flamegraph.pl --title="CPUフレームグラフ: 本番Webサーバー" \
--subtitle="99Hzで30秒間サンプリング" \
--width=1200 --colors=hot > cpu.svg
10.3 フレームグラフの種類
CPUフレームグラフ (on-CPU時間):
sudo perf record -F 99 -ag -- sleep 30
sudo perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > cpu.svg
Off-CPUフレームグラフ (ブロック/スリープ時間):
# BCCのoffcputimeツールを使用
sudo offcputime-bpfcc -df 30 > offcpu.folded
./flamegraph.pl --color=io --title="Off-CPUフレームグラフ" \
--countname=us < offcpu.folded > offcpu.svg
メモリアロケーションフレームグラフ:
# BCCのstackcountを使用
sudo stackcount-bpfcc -f -P 'kmem:kmalloc' > alloc.folded
./flamegraph.pl --color=mem --title="メモリアロケーションフレームグラフ" \
--countname=bytes < alloc.folded > alloc.svg
差分フレームグラフ (Hot/Cold):
# 変更前を記録
sudo perf record -F 99 -ag -o before.data -- sleep 30
sudo perf script -i before.data | ./stackcollapse-perf.pl > before.folded
# 変更後を記録
sudo perf record -F 99 -ag -o after.data -- sleep 30
sudo perf script -i after.data | ./stackcollapse-perf.pl > after.folded
# 差分フレームグラフの生成
./difffolded.pl before.folded after.folded | \
./flamegraph.pl --title="差分フレームグラフ" > diff.svg
10.4 フレームグラフの解釈
注目すべきポイント:
-
幅広いタワー(プラトー): サンプルに頻繁に出現する関数。CPUのホットスポットです。
-
幅広い底部、狭い上部: 頻繁に呼び出されるが、毎回異なる処理を行う関数。
-
欠落フレーム: スタックウォーキングの失敗。
fpの代わりに--call-graph dwarfを試してください。 -
[unknown]フレーム: シンボル情報の欠落。デバッグシンボルをインストールしてください。
-
カーネル対ユーザー空間の境界:
entry_SYSCALL_64を探して、syscallが行われている場所を確認。
11. eBPFアーキテクチャ
11.1 eBPFとは何か
Extended Berkeley Packet Filter (eBPF) は、カーネルソースコードを変更したりカーネルモジュールをロードしたりすることなく、Linuxカーネル内でサンドボックス化されたプログラムを実行できる革命的な技術です。
11.2 eBPFアーキテクチャ概要
┌─────────────────────────────────────────────────────────────────┐
│ ユーザー空間 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ bpftrace │ │ BCC │ │ libbpf │ │ カスタム │ │
│ │ │ │ (Python) │ │ (C/Rust) │ │ アプリ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ └──────────────┴──────┬───────┴──────────────┘ │
│ │ bpf() システムコール │
├─────────────────────────────┼───────────────────────────────────┤
│ カーネル空間 │
│ │ │
│ ┌────────▼────────┐ │
│ │ BPF ベリファイア │ ← 安全性チェック │
│ │ (DAG分析) │ │
│ └────────┬────────┘ │
│ │ 検証済みバイトコード │
│ ┌────────▼────────┐ │
│ │ JIT コンパイラ │ ← x86/ARMネイティブコード │
│ └────────┬────────┘ │
│ │ ネイティブ命令 │
│ ┌────────────────────┼────────────────────┐ │
│ │ │ │ │
│ ┌─────▼─────┐ ┌──────────▼──────┐ ┌─────────▼──────┐ │
│ │ Kprobes │ │ Tracepoints │ │ XDP/TC/cgroup │ │
│ │ Uprobes │ │ Perf Events │ │ Socket Filter │ │
│ │ fentry │ │ Raw Tracepoint │ │ Sched cls │ │
│ └───────────┘ └─────────────────┘ └────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ BPF Maps │ │
│ │ Hash │ Array │ Ring Buffer │ Perf Buffer │ LRU │ Stack │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
11.3 BPF命令セット
eBPFは11個のレジスタを持つRISCライクな命令セットを使用します:
レジスタ 目的
─────────────────────────────────────
r0 関数呼び出しとBPFプログラムからの戻り値
r1 第1引数 / コンテキストポインター
r2 第2引数
r3 第3引数
r4 第4引数
r5 第5引数
r6-r9 呼び出し先保存レジスタ
r10 フレームポインター(読み取り専用)
11.4 BPFベリファイア
BPFベリファイアは、BPFプログラムがカーネルをクラッシュさせたり侵害したりできないことを保証する安全メカニズムです:
検証チェック:
- DAGチェック: プログラムは有向非巡回グラフでなければならない(限定されない限りループ不可)
- 命令制限: 最大100万命令(ベリファイアの複雑さ制限)
- スタックサイズ: プログラムあたり最大512バイト
- レジスタ状態追跡: すべてのレジスタの型と値の範囲を追跡
- メモリアクセス安全性: すべてのポインターデリファレンスを検証
- ヘルパー関数検証: 引数が期待される型と一致する必要
- Mapアクセス検証: キーと値が正しいサイズである必要
- 無制限ループ禁止: ループは証明可能な制限を持つ必要(カーネル5.3以降)
- スリープ禁止: BPFプログラムはスリープ関数を呼べない
- 終了保証: プログラムは常に終了しなければならない
11.5 BPF Maps
BPF Mapsは、BPFプログラムとユーザー空間間で共有されるキー・バリューデータ構造です:
┌──────────────────┬──────────────────────────────────┐
│ BPF_MAP_TYPE_ │ 説明 │
├──────────────────┼──────────────────────────────────┤
│ HASH │ 汎用ハッシュテーブル │
│ ARRAY │ 固定サイズ配列 │
│ PROG_ARRAY │ BPFプログラム配列(テイルコール) │
│ PERF_EVENT_ARRAY │ CPU単位のperfイベントバッファ │
│ PERCPU_HASH │ CPU単位のハッシュテーブル │
│ PERCPU_ARRAY │ CPU単位の配列 │
│ STACK_TRACE │ スタックトレースストレージ │
│ LRU_HASH │ LRU退去ハッシュテーブル │
│ LPM_TRIE │ 最長プレフィックスマッチトライ │
│ RINGBUF │ リングバッファ │
│ TASK_STORAGE │ タスク単位のストレージ │
│ BLOOM_FILTER │ 空間効率的なセットメンバーシップ │
└──────────────────┴──────────────────────────────────┘
11.6 BPFヘルパー関数
/* よく使用されるBPFヘルパー */
/* 現在の時刻をナノ秒で取得 */
u64 ts = bpf_ktime_get_ns();
/* 現在のPID/TIDを取得 */
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
u32 tid = pid_tgid & 0xFFFFFFFF;
/* 現在のプロセス名を取得 */
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
/* カーネルメモリを安全に読み取り */
bpf_probe_read_kernel(&buf, sizeof(buf), kernel_ptr);
/* ユーザー空間メモリを安全に読み取り */
bpf_probe_read_user(&buf, sizeof(buf), user_ptr);
/* スタックトレースを取得 */
int stack_id = bpf_get_stackid(ctx, &stack_map, BPF_F_USER_STACK);
/* リングバッファに出力 */
bpf_ringbuf_output(&ringbuf, &data, sizeof(data), 0);
/* デバッグメッセージを出力 */
bpf_printk("debug: pid=%d value=%d\n", pid, value);
11.7 BPFリングバッファ vs Perfバッファ
/* モダンなアプローチ: BPFリングバッファ (カーネル5.8以降推奨) */
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024); /* 256 KB */
} ringbuf SEC(".maps");
struct event {
u32 pid;
char comm[16];
u64 timestamp;
};
SEC("kprobe/vfs_read")
int trace_read(struct pt_regs *ctx)
{
struct event *e;
/* リングバッファにスペースを予約 */
e = bpf_ringbuf_reserve(&ringbuf, sizeof(*e), 0);
if (!e)
return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
e->timestamp = bpf_ktime_get_ns();
/* イベントを送信 */
bpf_ringbuf_submit(e, 0);
return 0;
}
Perfバッファに対するリングバッファの利点:
- 単一の共有バッファ(CPU単位のバッファと比較)= メモリの無駄が少ない
reserve/submitAPIによるデータコピーの回避- 可変長レコードの効率的なサポート
- シングルプロデューサシナリオでのキャッシュ動作が良好
12. BPFプログラムタイプ
12.1 BPFプログラムタイプの概要
┌──────────────────────┬───────────────────────────────────────┐
│ タイプ │ アタッチメントポイント │
├──────────────────────┼───────────────────────────────────────┤
│ KPROBE │ Kprobe/Kretprobe │
│ TRACEPOINT │ カーネルtracepoints │
│ RAW_TRACEPOINT │ 生のtracepoints(引数コピーなし) │
│ PERF_EVENT │ Perfイベント(HW/SWカウンター) │
│ FENTRY/FEXIT │ 関数エントリ/イグジット(BTFベース) │
│ XDP │ ネットワークドライバーイングレス │
│ SCHED_CLS │ TCクラシファイア │
│ SOCKET_FILTER │ ソケットパケットフィルター │
│ SOCK_OPS │ ソケット操作コールバック │
│ CGROUP_SKB │ Cgroupソケットバッファ │
│ CGROUP_SOCK │ Cgroupソケット作成/bind/connect │
│ LSM │ Linuxセキュリティモジュールフック │
│ STRUCT_OPS │ カーネル構造体ops置換 │
│ ITER │ BPFイテレーター │
│ SYSCALL │ BPFシステムコールプログラム │
└──────────────────────┴───────────────────────────────────────┘
12.2 トレーシングプログラムタイプ
Fentry/Fexitプログラム(kprobeのモダンな代替):
/* Fentry/fexit: BTFベース、型安全、kprobesより低オーバーヘッド */
SEC("fentry/tcp_v4_connect")
int BPF_PROG(tcp_connect, struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
/* 型付き引数への直接アクセス - pt_regs不要 */
struct sockaddr_in *sin = (struct sockaddr_in *)uaddr;
u32 daddr = sin->sin_addr.s_addr;
u16 dport = sin->sin_port;
bpf_printk("connect to %pI4:%d\n", &daddr, bpf_ntohs(dport));
return 0;
}
SEC("fexit/tcp_v4_connect")
int BPF_PROG(tcp_connect_ret, struct sock *sk, struct sockaddr *uaddr,
int addr_len, int ret)
{
/* 引数と戻り値の両方にアクセス */
if (ret != 0)
bpf_printk("tcp_v4_connect failed: %d\n", ret);
return 0;
}
12.3 ネットワークプログラムタイプ
XDP (eXpress Data Path):
SEC("xdp")
int xdp_filter(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_DROP;
/* IPv4のみ処理 */
if (eth->h_proto != bpf_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_DROP;
/* 特定IPからのパケットをドロップ */
if (ip->saddr == bpf_htonl(0xC0A80164)) /* 192.168.1.100 */
return XDP_DROP;
return XDP_PASS;
}
/* XDP戻りコード:
* XDP_ABORTED - エラー、パケットドロップ
* XDP_DROP - ドライバーレベルでパケットドロップ
* XDP_PASS - 通常のネットワークスタックに渡す
* XDP_TX - 同じインターフェースから返送
* XDP_REDIRECT - 別のインターフェース/CPUにリダイレクト
*/
# XDPプログラムのアタッチ
sudo ip link set dev eth0 xdp obj xdp_filter.o sec xdp
# XDPプログラムのデタッチ
sudo ip link set dev eth0 xdp off
# XDP状態の表示
ip link show eth0
13. bpftrace: 言語とワンライナー
13.1 bpftraceの紹介
bpftraceは、eBPF上に構築されたLinux向けの高レベルトレーシング言語で、awk、C、DTraceにインスパイアされています。強力なトレーシングスクリプトを書くための簡潔な構文を提供します。
# インストール
# Debian/Ubuntu
sudo apt-get install bpftrace
# RHEL/CentOS/Fedora
sudo dnf install bpftrace
# インストールの確認
bpftrace --version
bpftrace --info
13.2 bpftrace言語の基本
プログラム構造:
プローブタイプ:プローブ名 /フィルター/ { アクション }
プローブタイプ:
kprobe/kretprobe - カーネル関数エントリ/リターン
uprobe/uretprobe - ユーザー空間関数エントリ/リターン
tracepoint - カーネルtracepoints
usdt - ユーザー空間静的プローブ
profile - 定期サンプリング
interval - 定期出力
software - ソフトウェアイベント
hardware - ハードウェアイベント
BEGIN/END - プログラム開始/終了
組み込み変数:
pid - プロセスID
tid - スレッドID
uid - ユーザーID
nsecs - ナノ秒タイムスタンプ
elapsed - bpftrace開始からのナノ秒
cpu - 現在のCPU番号
comm - プロセス名
kstack - カーネルスタックトレース
ustack - ユーザースタックトレース
arg0-argN - 関数引数
retval - 戻り値(retプローブ内)
func - 関数名
probe - 完全なプローブ名
curtask - 現在のtask_structポインター
13.3 必須bpftraceワンライナー
システム全体のトレーシング:
# 1. すべてのプローブをリスト
sudo bpftrace -l 'tracepoint:syscalls:*'
sudo bpftrace -l 'kprobe:tcp_*'
# 2. 新規プロセスのトレース
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("%-6d %-16s %s\n", pid, comm, str(args->filename));
}'
# 3. プロセス別のsyscallカウント
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter {
@[comm] = count();
}'
# 4. read()サイズのヒストグラム
sudo bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
@bytes = hist(args->ret);
}'
# 5. タイプ別のVFS呼び出しカウント
sudo bpftrace -e 'kprobe:vfs_* { @[func] = count(); }'
# 6. フルパス付きのファイルオープンをトレース
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
printf("%-6d %-16s %s\n", pid, comm, str(args->filename));
}'
CPUとスケジューリング:
# 7. カーネル関数別のCPU使用率プロファイル (49Hz)
sudo bpftrace -e 'profile:hz:49 {
@[kstack(5)] = count();
}'
# 8. プロセス別のCPU使用率
sudo bpftrace -e 'profile:hz:99 { @[comm] = count(); }'
# 9. スケジューラレイテンシヒストグラム
sudo bpftrace -e '
tracepoint:sched:sched_waking { @qtime[args->pid] = nsecs; }
tracepoint:sched:sched_switch /args->next_pid/ {
$ns = @qtime[args->next_pid];
if ($ns) {
@usecs = hist((nsecs - $ns) / 1000);
}
delete(@qtime[args->next_pid]);
}'
# 10. プロセス別のコンテキストスイッチカウント
sudo bpftrace -e 'tracepoint:sched:sched_switch {
@[args->prev_comm] = count();
}'
メモリ:
# 11. カーネルメモリアロケーションサイズ
sudo bpftrace -e 'tracepoint:kmem:kmalloc {
@bytes = hist(args->bytes_alloc);
}'
# 12. プロセス別のページフォールト
sudo bpftrace -e 'software:page-faults:1 { @[comm] = count(); }'
# 13. ユーザー空間malloc()サイズ
sudo bpftrace -e 'uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:malloc {
@size[comm] = hist(arg0);
}'
I/Oとディスク:
# 14. ブロックI/Oレイテンシヒストグラム
sudo bpftrace -e '
tracepoint:block:block_rq_issue { @start[args->dev, args->sector] = nsecs; }
tracepoint:block:block_rq_complete /@start[args->dev, args->sector]/ {
@usecs = hist((nsecs - @start[args->dev, args->sector]) / 1000);
delete(@start[args->dev, args->sector]);
}'
# 15. 低速ディスクI/O (>10ms)
sudo bpftrace -e '
tracepoint:block:block_rq_issue {
@start[args->dev, args->sector] = nsecs;
}
tracepoint:block:block_rq_complete /@start[args->dev, args->sector]/ {
$dur = (nsecs - @start[args->dev, args->sector]) / 1000000;
if ($dur > 10) {
printf("低速I/O: %dms dev=%d sector=%d bytes=%d\n",
$dur, args->dev, args->sector, args->nr_sector * 512);
}
delete(@start[args->dev, args->sector]);
}'
ネットワーク:
# 16. TCP接続追跡
sudo bpftrace -e '
kprobe:tcp_v4_connect {
@start[tid] = nsecs;
}
kretprobe:tcp_v4_connect /@start[tid]/ {
$dur = (nsecs - @start[tid]) / 1000;
printf("%-6d %-16s 接続レイテンシ: %d us (ret=%d)\n",
pid, comm, $dur, retval);
delete(@start[tid]);
}'
# 17. TCP再送
sudo bpftrace -e 'tracepoint:tcp:tcp_retransmit_skb {
printf("%-6d %-16s %s:%d -> %s:%d state=%d\n",
pid, comm,
ntop(args->saddr), args->sport,
ntop(args->daddr), args->dport,
args->state);
}'
# 18. ネットワークパケットサイズ分布
sudo bpftrace -e 'tracepoint:net:net_dev_xmit {
@bytes[args->name] = hist(args->len);
}'
14. BCC: BPFコンパイラコレクション
14.1 BCCの紹介
BCC (BPF Compiler Collection) は、効率的なカーネルトレーシングと操作プログラムを作成するためのツールキットです。すぐに使える包括的なツールセットと、カスタムBPFプログラムを書くためのPython/Luaフレームワークを含みます。
# インストール
# Debian/Ubuntu
sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
# RHEL/CentOS/Fedora
sudo dnf install bcc-tools
# Pythonバインディング
sudo apt-get install python3-bpfcc
# ツールは通常 /usr/share/bcc/tools/ にインストール
# または *-bpfcc コマンドとしてアクセス可能
ls /usr/share/bcc/tools/ | head -20
14.2 主要なBCCツール
プロセスとCPUツール:
# execsnoop - 新規プロセス実行をトレース
sudo execsnoop-bpfcc
# exitsnoop - プロセス終了を年齢と終了コード付きでトレース
sudo exitsnoop-bpfcc
# runqlat - スケジューラランキューレイテンシヒストグラム
sudo runqlat-bpfcc
# runqlen - スケジューラランキュー長ヒストグラム
sudo runqlen-bpfcc
# cpudist - タスクあたりのon-CPU時間分布
sudo cpudist-bpfcc
# offcputime - スタックトレース付きOff-CPU時間
sudo offcputime-bpfcc -df 10 > offcpu.folded
# profile - CPUプロファイリング(サンプリング)
sudo profile-bpfcc -df 30 > profile.folded
# funccount - 関数呼び出しカウント
sudo funccount-bpfcc 'vfs_*'
sudo funccount-bpfcc -i 1 'tcp_*' # 秒あたり
# funclatency - 関数レイテンシヒストグラム
sudo funclatency-bpfcc vfs_read
sudo funclatency-bpfcc -u vfs_read # マイクロ秒
I/Oとファイルシステムツール:
# biolatency - ブロックI/Oレイテンシヒストグラム
sudo biolatency-bpfcc
# biosnoop - ブロックI/Oトレーシング(イベント単位)
sudo biosnoop-bpfcc
# biotop - ブロックI/O top(ディスクI/O向けtop風)
sudo biotop-bpfcc
# ext4slower - 低速ext4ファイルシステム操作をトレース
sudo ext4slower-bpfcc 10 # 閾値(ms)
# filelife - ファイルの作成と削除をトレース
sudo filelife-bpfcc
# filetop - ファイル名別の読み書き(top形式)
sudo filetop-bpfcc
# fileslower - 低速ファイル操作をトレース
sudo fileslower-bpfcc 10 # >10ms
# cachestat - ページキャッシュのヒット/ミス統計
sudo cachestat-bpfcc
# HITS MISSES DIRTIES HITRATIO BUFFERS_MB CACHED_MB
# 12345 234 45 98.14% 123 4567
# cachetop - ページキャッシュ統計(プロセス別、top形式)
sudo cachetop-bpfcc
# mountsnoop - mount/umount操作をトレース
sudo mountsnoop-bpfcc
メモリツール:
# memleak - メモリアロケーションをトレースしリークを検出
sudo memleak-bpfcc -p $(pgrep my_app)
# [12:00:00] 残存アロケーションの上位10スタック:
# 1024 bytes in 4 allocations from stack
# my_app+0x1234
# process_request+0x56
# main+0x78
# 特定のアロケーション関数でmemleak
sudo memleak-bpfcc -p $(pgrep my_app) -a malloc
# oomkill - OOMキラーイベントをトレース
sudo oomkill-bpfcc
# slabratetop - 上位SLABキャッシュアロケーション率
sudo slabratetop-bpfcc
ネットワークツール:
# tcpconnect - TCPアクティブ接続をトレース
sudo tcpconnect-bpfcc
# tcpaccept - TCPパッシブ接続をトレース
sudo tcpaccept-bpfcc
# tcpretrans - TCP再送をトレース
sudo tcpretrans-bpfcc
# tcplife - TCPセッションライフスパンをトレース
sudo tcplife-bpfcc
# tcptop - 接続別TCPスループット(top形式)
sudo tcptop-bpfcc
# tcpdrop - TCP パケットドロップを理由付きでトレース
sudo tcpdrop-bpfcc
# tcpstates - TCPセッション状態変化をトレース
sudo tcpstates-bpfcc
# softirqs - ソフト割り込み(softirq)時間を測定
sudo softirqs-bpfcc
# hardirqs - ハード割り込み時間を測定
sudo hardirqs-bpfcc
14.3 カスタムBCCプログラムの作成
#!/usr/bin/env python3
# tcp_latency.py - BCCでTCP接続レイテンシをトレース
from bcc import BPF
from time import strftime
# BPFプログラム
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <net/tcp_states.h>
#include <bcc/proto.h>
struct event_t {
u32 pid;
u32 daddr;
u16 dport;
u64 delta_us;
char comm[16];
};
BPF_HASH(start, u32, u64);
BPF_PERF_OUTPUT(events);
int trace_connect_entry(struct pt_regs *ctx, struct sock *sk)
{
u32 tid = bpf_get_current_pid_tgid();
u64 ts = bpf_ktime_get_ns();
start.update(&tid, &ts);
return 0;
}
int trace_connect_return(struct pt_regs *ctx)
{
u32 tid = bpf_get_current_pid_tgid();
u64 *tsp = start.lookup(&tid);
if (!tsp)
return 0;
u64 delta = bpf_ktime_get_ns() - *tsp;
start.delete(&tid);
/* 1ms超のみレポート */
if (delta < 1000000)
return 0;
struct event_t event = {};
event.pid = bpf_get_current_pid_tgid() >> 32;
event.delta_us = delta / 1000;
bpf_get_current_comm(&event.comm, sizeof(event.comm));
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
event.daddr = sk->__sk_common.skc_daddr;
event.dport = sk->__sk_common.skc_dport;
events.perf_submit(ctx, &event, sizeof(event));
return 0;
}
"""
b = BPF(text=bpf_text)
b.attach_kprobe(event="tcp_v4_connect", fn_name="trace_connect_entry")
b.attach_kretprobe(event="tcp_v4_connect", fn_name="trace_connect_return")
# ヘッダー出力
print("%-9s %-7s %-16s %-16s %-6s %s" % (
"TIME", "PID", "COMM", "DADDR", "DPORT", "LAT(ms)"))
def print_event(cpu, data, size):
event = b["events"].event(data)
import socket, struct
daddr = socket.inet_ntoa(struct.pack("I", event.daddr))
dport = socket.ntohs(event.dport)
print("%-9s %-7d %-16s %-16s %-6d %.2f" % (
strftime("%H:%M:%S"),
event.pid,
event.comm.decode('utf-8', 'replace'),
daddr,
dport,
event.delta_us / 1000.0))
b["events"].open_perf_buffer(print_event)
while True:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
break
14.4 BCCツール一覧表
| ツール | 目的 | 使用例 |
|---|---|---|
execsnoop | 新規プロセスの追跡 | execsnoop-bpfcc -t |
biolatency | ブロックI/Oレイテンシ | biolatency-bpfcc -D |
biosnoop | ブロックI/Oイベント | biosnoop-bpfcc -d sda |
cachestat | ページキャッシュ統計 | cachestat-bpfcc 1 |
funccount | 関数呼び出しカウント | funccount-bpfcc 'tcp_*' |
funclatency | 関数レイテンシ | funclatency-bpfcc vfs_read |
memleak | メモリリーク検出 | memleak-bpfcc -p PID |
offcputime | Off-CPUスタックトレース | offcputime-bpfcc -df 10 |
opensnoop | ファイルオープンの追跡 | opensnoop-bpfcc -p PID |
profile | CPUプロファイリング | profile-bpfcc -df 30 |
runqlat | ランキューレイテンシ | runqlat-bpfcc |
tcpconnect | TCP接続(アクティブ) | tcpconnect-bpfcc -t |
tcplife | TCPセッション | tcplife-bpfcc |
tcpretrans | TCP再送 | tcpretrans-bpfcc |
15. libbpfとCO-RE
15.1 libbpfの紹介
libbpfは、BPFプログラムとMapsのロード、検証、管理のための標準BPFライブラリです。モダンなBPFアプリケーション開発の基盤となるC APIを提供します。
15.2 CO-RE: Compile Once, Run Everywhere(一度コンパイルし、どこでも実行)
CO-REはBPFの移植性の問題を解決します。以前は、BPFプログラムは正確なカーネルヘッダーに一致させるために、ターゲットマシン上でコンパイルする必要がありました。CO-REは以下を可能にします:
- BTF (BPF Type Format): カーネルに埋め込まれたコンパクトな型情報
- 再配置: libbpfがロード時に構造体フィールドのオフセットを調整
- BPF CO-REヘルパー: ポータブルな構造体アクセスのための
bpf_core_read()
# カーネルがBTFをサポートしているか確認
ls -la /sys/kernel/btf/vmlinux
# vmlinux.hの生成(すべてのカーネル型)
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
15.3 完全なlibbpf + CO-REの例
BPFプログラム (opensnoop.bpf.c):
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#define TASK_COMM_LEN 16
#define NAME_MAX 255
struct event {
__u32 pid;
__u32 uid;
int ret;
char comm[TASK_COMM_LEN];
char fname[NAME_MAX];
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} rb SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_openat")
int tracepoint__syscalls__sys_enter_openat(
struct trace_event_raw_sys_enter *ctx)
{
__u32 tid = (__u32)bpf_get_current_pid_tgid();
const char *fname_ptr = (const char *)ctx->args[1];
// ファイル名ポインタを保存してsys_exitで使用
// ... (省略 - 英語版と同様の実装)
return 0;
}
char LICENSE[] SEC("license") = "GPL";
ユーザー空間プログラム (opensnoop.c):
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <bpf/libbpf.h>
#include "opensnoop.skel.h"
static volatile bool exiting = false;
static void sig_handler(int sig)
{
exiting = true;
}
static int handle_event(void *ctx, void *data, size_t data_sz)
{
const struct event *e = data;
printf("%-8d %-8d %-4d %-16s %s\n",
e->pid, e->uid, e->ret, e->comm, e->fname);
return 0;
}
int main(int argc, char **argv)
{
struct opensnoop_bpf *skel;
struct ring_buffer *rb = NULL;
int err;
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
/* BPFアプリケーションを開く */
skel = opensnoop_bpf__open();
if (!skel) {
fprintf(stderr, "BPFスケルトンのオープンに失敗\n");
return 1;
}
/* BPFプログラムのロードと検証 */
err = opensnoop_bpf__load(skel);
if (err) {
fprintf(stderr, "BPFスケルトンのロードに失敗: %d\n", err);
goto cleanup;
}
/* Tracepointsのアタッチ */
err = opensnoop_bpf__attach(skel);
if (err) {
fprintf(stderr, "BPFスケルトンのアタッチに失敗: %d\n", err);
goto cleanup;
}
/* リングバッファのセットアップ */
rb = ring_buffer__new(bpf_map__fd(skel->maps.rb),
handle_event, NULL, NULL);
printf("%-8s %-8s %-4s %-16s %s\n",
"PID", "UID", "RET", "COMM", "FILENAME");
while (!exiting) {
err = ring_buffer__poll(rb, 100);
if (err == -EINTR) break;
}
cleanup:
ring_buffer__free(rb);
opensnoop_bpf__destroy(skel);
return err < 0 ? 1 : 0;
}
ビルドシステム (Makefile):
CLANG ?= clang
BPFTOOL ?= bpftool
ARCH := $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')
all: opensnoop
vmlinux.h:
$(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > $@
opensnoop.bpf.o: opensnoop.bpf.c vmlinux.h
$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) -c $< -o $@
opensnoop.skel.h: opensnoop.bpf.o
$(BPFTOOL) gen skeleton $< > $@
opensnoop: opensnoop.c opensnoop.skel.h
$(CC) -g -O2 -c $< -o opensnoop.o
$(CC) opensnoop.o -lbpf -lelf -lz -o $@
15.4 BPF CO-REフィールドアクセス
/* 従来のアプローチ(ポータブルでない) */
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
int pid;
bpf_probe_read_kernel(&pid, sizeof(pid), &task->pid);
/* CO-REアプローチ(カーネルバージョン間でポータブル) */
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
int pid = BPF_CORE_READ(task, pid);
/* ネストされた構造体アクセス */
const char *name = BPF_CORE_READ(task, mm, exe_file, f_path.dentry, d_name.name);
/* フィールドの存在チェック(カーネルバージョン互換性) */
if (bpf_core_field_exists(task->io_uring)) {
/* io_uringフィールドが存在する場合のみアクセス */
}
16. SystemTap概要
16.1 SystemTapとは何か
SystemTap (stap) は、実行中のLinuxシステムを動的に計装するためのスクリプト言語とインフラストラクチャです。eBPFに先行し、強力なスクリプティングインターフェースを提供します。
# インストール
# Debian/Ubuntu
sudo apt-get install systemtap systemtap-runtime
# RHEL/CentOS/Fedora
sudo dnf install systemtap systemtap-runtime
# 必須: カーネルデバッグ情報
sudo apt-get install linux-image-$(uname -r)-dbgsym
# インストールのテスト
sudo stap -e 'probe begin { println("Hello, SystemTap!") exit() }'
16.2 SystemTapの例
# ファイルオープンのトレース
sudo stap -e '
probe syscall.openat {
printf("%s[%d] openat(%s)\n", execname(), pid(), filename)
}'
# 上位10システムコール
sudo stap -e '
global syscalls
probe syscall.* {
syscalls[name]++
}
probe timer.s(10) {
foreach (name in syscalls- limit 10)
printf("%-20s %d\n", name, syscalls[name])
exit()
}'
16.3 SystemTap vs eBPF 比較
| 特徴 | SystemTap | eBPF/bpftrace |
|---|---|---|
| カーネルサポート | 2.6+ | 3.18+(完全: 4.x+) |
| 安全性 | カーネルモジュール(リスクあり) | ベリファイア(安全) |
| パフォーマンス | 良好 | 優秀 |
| 言語 | SystemTapスクリプト | bpftrace / C |
| 要件 | デバッグ情報、カーネルヘッダー | BTF(CO-RE用) |
| 本番使用 | 限定的な採用 | 広範囲 |
| コミュニティ | 衰退傾向 | 急速に成長中 |
| 主な使用例 | レガシーシステム、複雑なスクリプト | モダンなトレーシング |
17. /sys/kernel/debug/tracing/ インターフェース
17.1 完全なtracefs リファレンス
/sys/kernel/debug/tracing/
├── available_tracers # コンパイル済みトレーサー一覧
├── current_tracer # アクティブなトレーサー(書き込みで変更)
├── tracing_on # マスタースイッチ(1=オン、0=オフ)
├── trace # トレースバッファ読み出し(非消費)
├── trace_pipe # トレースバッファ読み出し(消費、ブロッキング)
├── trace_options # トレース出力フォーマットオプション
├── buffer_size_kb # CPU単位リングバッファサイズ
├── trace_marker # ユーザー空間からのイベント書き込み
│
├── set_ftrace_filter # ファンクショントレーサーの関数フィルター
├── set_ftrace_notrace # 関数除外フィルター
├── set_ftrace_pid # PIDフィルター
├── set_graph_function # function_graphの関数フィルター
├── max_graph_depth # function_graphの最大呼び出し深さ
├── tracing_thresh # function_graphの期間閾値
│
├── available_events # すべての利用可能なトレースイベント
├── events/ # イベントディレクトリ
│ ├── enable # すべてのイベントの有効/無効
│ ├── sched/ # スケジューライベント
│ │ ├── enable # すべてのschedイベントの有効/無効
│ │ ├── filter # すべてのschedイベントのフィルター
│ │ ├── sched_switch/ # 個別イベント
│ │ │ ├── enable # このイベントの有効/無効
│ │ │ ├── filter # イベントフィルター
│ │ │ ├── format # イベントフォーマット定義
│ │ │ ├── id # イベント数値ID
│ │ │ └── trigger # イベントトリガー
│ │ └── ...
│ └── ...
│
├── kprobe_events # kprobeベースイベントの定義
├── uprobe_events # uprobeベースイベントの定義
├── dynamic_events # 統一動的イベントインターフェース
├── synthetic_events # 合成イベントの定義
│
├── instances/ # 名前付きトレーシングインスタンス
│
├── per_cpu/ # CPU単位のトレースデータ
├── snapshot # トレースバッファのスナップショット
├── stack_trace # スタックトレースキャプチャ
├── saved_cmdlines # PIDからコマンド名へのマッピング
└── README # ヘルプドキュメント
17.2 ユーザー空間トレースマーカー
アプリケーションはカーネルトレースバッファにイベントを注入できます:
/* Cアプリケーション: trace_markerへの書き込み */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
static int trace_fd = -1;
void trace_init(void)
{
trace_fd = open("/sys/kernel/debug/tracing/trace_marker", O_WRONLY);
}
void trace_write(const char *fmt, ...)
{
if (trace_fd < 0) return;
char buf[256];
va_list ap;
va_start(ap, fmt);
int len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
write(trace_fd, buf, len);
}
/* 使用方法 */
int main(void)
{
trace_init();
trace_write("request_start: id=%d url=%s", 42, "/api/data");
/* ... リクエスト処理 ... */
trace_write("request_end: id=%d status=%d latency_us=%d",
42, 200, 1500);
return 0;
}
# シェルからtrace_markerへの書き込み
echo "my_event: 処理開始" > /sys/kernel/debug/tracing/trace_marker
# ... 何かを実行 ...
echo "my_event: 完了" > /sys/kernel/debug/tracing/trace_marker
# トレース出力でマーカーを表示
cat /sys/kernel/debug/tracing/trace | grep my_event
18. 実践的デバッグシナリオ
18.1 シナリオ1: レイテンシスパイクの診断
問題: Webアプリケーションが数分ごとにレイテンシスパイクを経験する。
調査アプローチ:
# ステップ1: ベースラインパフォーマンスメトリクスの取得
sudo perf stat -a -I 1000 -e cycles,instructions,cache-misses,\
context-switches,page-faults -- sleep 60
# ステップ2: スケジューラレイテンシの確認
sudo runqlat-bpfcc -m 60
# 高レイテンシの場合: CPU競合
# ステップ3: アプリケーションのOff-CPU時間をトレース
sudo offcputime-bpfcc -p $(pgrep my_app) -f 30 > offcpu.folded
# Off-CPUフレームグラフの生成
flamegraph.pl --color=io < offcpu.folded > offcpu.svg
# ステップ4: ロック競合の確認
sudo bpftrace -e '
uprobe:/usr/lib/x86_64-linux-gnu/libpthread.so.0:pthread_mutex_lock {
@lock_start[tid] = nsecs;
}
uretprobe:/usr/lib/x86_64-linux-gnu/libpthread.so.0:pthread_mutex_lock
/@lock_start[tid]/ {
$dur = (nsecs - @lock_start[tid]) / 1000;
if ($dur > 1000) {
printf("低速ロック: pid=%d comm=%s dur=%d us\n", pid, comm, $dur);
print(ustack);
}
@lock_us = hist($dur);
delete(@lock_start[tid]);
}'
# ステップ5: I/Oレイテンシの確認
sudo biosnoop-bpfcc -d sda 60 | awk '$NF > 10 {print}'
# ステップ6: メモリ圧迫の確認
sudo bpftrace -e '
tracepoint:vmscan:mm_vmscan_direct_reclaim_begin {
@direct_reclaim[comm] = count();
@reclaim_start[tid] = nsecs;
}
tracepoint:vmscan:mm_vmscan_direct_reclaim_end /@reclaim_start[tid]/ {
@reclaim_us = hist((nsecs - @reclaim_start[tid]) / 1000);
delete(@reclaim_start[tid]);
}'
18.2 シナリオ2: CPU使用率の調査
問題: サーバーが予期せず高いCPU使用率を示す。
# ステップ1: ホットプロセスの特定
sudo perf top -g
# ステップ2: ホットプロセスのCPUプロファイルを記録
sudo perf record -F 99 -g --call-graph dwarf -p $(pgrep my_app) -- sleep 30
# ステップ3: フレームグラフの生成
sudo perf script | stackcollapse-perf.pl | flamegraph.pl > cpu.svg
# ステップ4: 過剰なsyscallの確認
sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter /pid == cpid/ {
@syscalls = count();
@by_id[args->id] = count();
}
interval:s:1 {
printf("syscalls/sec: ");
print(@syscalls);
clear(@syscalls);
}'
# ステップ5: CPU周波数とスロットリングの監視
sudo turbostat --interval 1 --num_iterations 10
18.3 シナリオ3: メモリリーク検出
問題: アプリケーションのメモリが時間とともに継続的に増加する。
# ステップ1: メモリ増加の監視
watch -n 1 'ps -o pid,rss,vsz,comm -p $(pgrep my_app)'
# ステップ2: memleak (BCC) でアロケーションを追跡
sudo memleak-bpfcc -p $(pgrep my_app) --combined-only -o 10000
# ステップ3: bpftraceで追跡
sudo bpftrace -e '
uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:malloc {
@alloc_size[tid] = arg0;
}
uretprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:malloc
/@alloc_size[tid]/ {
@outstanding[retval] = @alloc_size[tid];
@alloc_stacks[ustack(8)] = sum(@alloc_size[tid]);
delete(@alloc_size[tid]);
}
uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:free
/arg0 != 0/ {
delete(@outstanding[arg0]);
}
interval:s:10 {
printf("--- アロケーションレポート ---\n");
printf("\n上位アロケーションスタック:\n");
print(@alloc_stacks, 5);
clear(@alloc_stacks);
}'
# ステップ4: カーネルメモリリーク(SLAB)の確認
sudo slabratetop-bpfcc 10
18.4 シナリオ4: I/Oボトルネックの調査
問題: アプリケーションが低速なI/Oパフォーマンスを経験する。
# ステップ1: 全体的なI/O統計
sudo iostat -xz 1
# ステップ2: ブロックI/Oレイテンシ分布
sudo biolatency-bpfcc -D
# ディスク別のレイテンシヒストグラムを表示
# ステップ3: 個別のI/Oリクエストをトレース
sudo biosnoop-bpfcc -d sda
# ステップ4: I/Oを引き起こしているプロセスを特定
sudo biotop-bpfcc
# ステップ5: ファイルシステムレイテンシ
sudo ext4slower-bpfcc 1 # >1msの操作を表示
# ステップ6: ライトバック圧迫の確認
sudo bpftrace -e '
tracepoint:writeback:writeback_pages_written {
@pages = hist(args->pages);
}'
18.5 シナリオ5: ネットワークパフォーマンスデバッグ
# ステップ1: TCP接続レイテンシの追跡
sudo tcpconnect-bpfcc -Lt
# ステップ2: TCP再送の監視
sudo tcpretrans-bpfcc -l
# ステップ3: TCP状態の追跡
sudo tcpstates-bpfcc
# ステップ4: 接続別ネットワークスループット
sudo tcptop-bpfcc
# ステップ5: DNS解決時間の追跡
sudo bpftrace -e '
uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo {
@start[tid] = nsecs;
@host[tid] = arg0;
}
uretprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo /@start[tid]/ {
$dur = (nsecs - @start[tid]) / 1000;
printf("DNS: %-16s %d us (ret=%d)\n",
str(@host[tid]), $dur, retval);
@dns_us = hist($dur);
delete(@start[tid]);
delete(@host[tid]);
}'
# ステップ6: ドロップされたパケットの確認
sudo bpftrace -e '
tracepoint:skb:kfree_skb {
@drops[args->protocol, args->location] = count();
}
interval:s:10 {
print(@drops);
clear(@drops);
}'
18.6 包括的デバッグランブック
#!/bin/bash
# system_health_check.sh - 包括的なシステムヘルスチェック
DURATION=${1:-30}
OUTPUT_DIR="health_check_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$OUTPUT_DIR"
echo "=== システムヘルスチェック (${DURATION}秒) ==="
echo "出力ディレクトリ: $OUTPUT_DIR"
# 1. システム概要
echo "[1/8] システム概要を収集中..."
uname -a > "$OUTPUT_DIR/system_info.txt"
uptime >> "$OUTPUT_DIR/system_info.txt"
free -h >> "$OUTPUT_DIR/system_info.txt"
# 2. CPU統計
echo "[2/8] CPU統計を記録中..."
sudo perf stat -a -o "$OUTPUT_DIR/perf_stat.txt" -- sleep "$DURATION" &
# 3. CPUプロファイル
echo "[3/8] CPUプロファイルを記録中..."
sudo perf record -F 99 -ag --call-graph dwarf \
-o "$OUTPUT_DIR/perf.data" -- sleep "$DURATION" &
# 4. スケジューラレイテンシ
echo "[4/8] スケジューラレイテンシを記録中..."
sudo runqlat-bpfcc "$DURATION" 1 > "$OUTPUT_DIR/runqlat.txt" 2>&1 &
# 5. ブロックI/Oレイテンシ
echo "[5/8] I/Oレイテンシを記録中..."
sudo biolatency-bpfcc -D "$DURATION" 1 > "$OUTPUT_DIR/biolatency.txt" 2>&1 &
# 6. TCP接続
echo "[6/8] TCP接続を記録中..."
timeout "$DURATION" sudo tcplife-bpfcc > "$OUTPUT_DIR/tcplife.txt" 2>&1 &
# 7. ページキャッシュ統計
echo "[7/8] ページキャッシュ統計を記録中..."
sudo cachestat-bpfcc 1 "$DURATION" > "$OUTPUT_DIR/cachestat.txt" 2>&1 &
# 8. プロセス実行
echo "[8/8] 新規プロセスを記録中..."
timeout "$DURATION" sudo execsnoop-bpfcc -t > "$OUTPUT_DIR/execsnoop.txt" 2>&1 &
# すべてのバックグラウンドジョブを待つ
wait
# フレームグラフの生成
echo "フレームグラフを生成中..."
sudo perf script -i "$OUTPUT_DIR/perf.data" | \
stackcollapse-perf.pl 2>/dev/null | \
flamegraph.pl > "$OUTPUT_DIR/cpu_flamegraph.svg" 2>/dev/null
echo "=== ヘルスチェック完了 ==="
echo "結果: $OUTPUT_DIR/"
ls -lh "$OUTPUT_DIR/"
19. ツールリファレンスと比較
19.1 ツール選択マトリックス
| シナリオ | 主要ツール | 副次ツール | 使用タイミング |
|---|---|---|---|
| CPUプロファイリング | perf record/report | profile-bpfcc | On-CPUパフォーマンス分析 |
| Off-CPU分析 | offcputime-bpfcc | bpftrace | スレッドブロッキング調査 |
| フレームグラフ | perf script + FlameGraph | profile-bpfcc -df | 視覚的パフォーマンス分析 |
| スケジューラ分析 | perf sched | runqlat-bpfcc | スケジューリングレイテンシ |
| I/Oレイテンシ | biolatency-bpfcc | biosnoop-bpfcc | ディスクパフォーマンス |
| ファイルシステム | ext4slower-bpfcc | fileslower-bpfcc | ファイルシステム操作 |
| メモリリーク | memleak-bpfcc | bpftrace | アロケーション追跡 |
| TCP接続 | tcpconnect-bpfcc | tcplife-bpfcc | ネットワークデバッグ |
| Syscallトレーシング | perf trace | strace | システムコール分析 |
| 関数トレーシング | ftrace / trace-cmd | bpftrace | カーネル関数フロー |
| カスタム分析 | bpftrace | BCC (Python) | アドホック調査 |
| 本番監視 | libbpf (CO-RE) | BCC | 長時間実行監視 |
| パケット処理 | XDP (eBPF) | TC (eBPF) | 高性能ネットワーキング |
| レガシーシステム | SystemTap | ftrace | 古いカーネルバージョン |
19.2 turbostat: CPU電力と周波数
# 基本的なturbostat出力
sudo turbostat --interval 1
# 特定の列を表示
sudo turbostat --show Core,CPU,Avg_MHz,Busy%,Bzy_MHz,TSC_MHz,\
IRQ,C1%,C6%,PkgWatt,CorWatt
# 10回分を監視
sudo turbostat --interval 1 --num_iterations 10
19.3 クイックリファレンス: よく使うコマンド
# === perf ===
perf stat ./program # イベントカウント
perf record -g ./program # プロファイル記録
perf report # プロファイル分析
perf top -g # リアルタイムプロファイリング
perf trace ./program # syscallトレース
perf sched record sleep 10 # スケジューラ記録
# === ftrace / trace-cmd ===
trace-cmd record -e sched sleep 5 # イベント記録
trace-cmd report # トレース分析
trace-cmd profile sleep 10 # システムプロファイル
# === bpftrace ===
bpftrace -e 'kprobe:vfs_read { @[comm] = count(); }'
bpftrace -e 'profile:hz:99 { @[kstack] = count(); }'
# === BCCツール ===
execsnoop-bpfcc # 新規プロセス
opensnoop-bpfcc # ファイルオープン
biolatency-bpfcc # I/Oレイテンシ
tcpconnect-bpfcc # TCP接続
runqlat-bpfcc # スケジューラレイテンシ
profile-bpfcc -df 30 # CPUプロファイル(フォールド)
memleak-bpfcc -p PID # メモリリーク
funccount-bpfcc 'vfs_*' # 関数カウント
# === bpftool ===
bpftool prog list # BPFプログラム一覧
bpftool map list # BPF Maps一覧
bpftool net list # ネットワークBPF一覧
bpftool btf list # BTFオブジェクト一覧
20. 付録: カーネル設定とセットアップ
20.1 必要なカーネル設定
# コアトレーシング
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_DYNAMIC_FTRACE=y
# イベントトレーシング
CONFIG_TRACING=y
CONFIG_EVENT_TRACING=y
# プローブ
CONFIG_KPROBES=y
CONFIG_KRETPROBES=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y
CONFIG_KPROBE_EVENTS=y
# eBPF
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
# BTF (CO-RE用)
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_BTF_MODULES=y
# perf
CONFIG_PERF_EVENTS=y
CONFIG_HW_PERF_EVENTS=y
# スタックトレーシング
CONFIG_STACKTRACE=y
CONFIG_FRAME_POINTER=y
# デバッグ情報
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF5=y
# ロックデバッグ(perf lock用)
CONFIG_LOCK_STAT=y
# ヒストグラムトリガー
CONFIG_HIST_TRIGGERS=y
20.2 トレーシング用sysctl設定
# 非特権perfイベントを許可(非rootプロファイリング用)
sudo sysctl kernel.perf_event_paranoid=-1
# BPF JITを有効化
sudo sysctl net.core.bpf_jit_enable=1
# /proc/kallsymsでカーネルアドレスを表示
sudo sysctl kernel.kptr_restrict=0
# 永続設定 (/etc/sysctl.d/99-tracing.conf)
cat <<EOF | sudo tee /etc/sysctl.d/99-tracing.conf
kernel.perf_event_paranoid = -1
net.core.bpf_jit_enable = 1
kernel.kptr_restrict = 0
EOF
sudo sysctl --system
20.3 トレーシングツールのインストール
# === Debian/Ubuntu ===
sudo apt-get update
sudo apt-get install -y \
linux-tools-common \
linux-tools-$(uname -r) \
bpfcc-tools \
bpftrace \
trace-cmd \
kernelshark \
systemtap \
systemtap-runtime \
linux-headers-$(uname -r) \
python3-bpfcc
# === RHEL/CentOS/Fedora ===
sudo dnf install -y \
perf \
bcc-tools \
bpftrace \
trace-cmd \
kernelshark \
systemtap \
systemtap-runtime \
kernel-devel \
kernel-headers
# === FlameGraphツールのビルド ===
git clone https://github.com/brendangregg/FlameGraph.git ~/FlameGraph
# === libbpf-bootstrapの例をビルド ===
git clone --recurse-submodules \
https://github.com/libbpf/libbpf-bootstrap.git
cd libbpf-bootstrap/examples/c
make
20.4 よくある問題のトラブルシューティング
# 問題: "perf: command not found"
# 解決策: linux-tools-$(uname -r) をインストール
sudo apt-get install linux-tools-$(uname -r)
# 問題: "Access to performance monitoring denied"
# 解決策: perf_event_paranoidを設定
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
# 問題: perfで "[unknown]" シンボル
# 解決策: デバッグシンボルをインストール
sudo apt-get install linux-image-$(uname -r)-dbgsym
sudo apt-get install libc6-dbg
# 問題: "BPF JIT is not enabled"
# 解決策:
echo 1 | sudo tee /proc/sys/net/core/bpf_jit_enable
# 問題: "Cannot find BTF for kernel"
# 解決策: カーネル設定でCONFIG_DEBUG_INFO_BTF=yを確認
zcat /proc/config.gz | grep CONFIG_DEBUG_INFO_BTF
# 問題: "kprobe failed: No such file or directory"
# 解決策: 関数がインライン化されている可能性。利用可能な関数を確認:
cat /sys/kernel/debug/tracing/available_filter_functions | grep func_name
# 問題: "Ring buffer full" 警告
# 解決策: バッファサイズを増加
echo 16384 > /sys/kernel/debug/tracing/buffer_size_kb
# 問題: tracefs アクセスの "Permission denied"
# 解決策:
sudo mount -o remount,mode=755 /sys/kernel/debug
参考文献
- Brendan Gregg著 "BPF Performance Tools" (Addison-Wesley, 2019)
- Brendan Gregg著 "Systems Performance: Enterprise and the Cloud" 第2版 (Addison-Wesley, 2020)
- Linuxカーネルドキュメント: https://www.kernel.org/doc/html/latest/trace/
- BPFドキュメント: https://docs.kernel.org/bpf/
- bpftraceリファレンスガイド: https://github.com/bpftrace/bpftrace/blob/master/docs/reference_guide.md
- BCCツールドキュメント: https://github.com/iovisor/bcc
- libbpf-bootstrap: https://github.com/libbpf/libbpf-bootstrap
- FlameGraph: https://github.com/brendangregg/FlameGraph
- perf wiki: https://perf.wiki.kernel.org/
- trace-cmdドキュメント: https://trace-cmd.org/
- Brendan Greggのブログ: https://www.brendangregg.com/
- LWN.net eBPF記事: https://lwn.net/Kernel/Index/#BPF
- eBPF.io: https://ebpf.io/
このドキュメントは、Linuxカーネルトレーシングおよびプロファイリング技術の包括的な技術リファレンスとして生成されました。最終更新: 2026年4月