Revisiting FTrace

ふと思い立って ftrace 周辺のドキュメントを軽く冷やかしてみる。数年前から Sphinx ベースになったらしい・・・。

自分の五年前の理解は、間違ってはいないがだいぶ雑だった。

FTrace はもともとはカーネルのコードで GCC のプロファイラオプションというか gprof を使えるようにするというもので、ただカーネル空間にはプロファイラのランタイムがないのでカーネルが自前のランタイムを提供していた、らしい。そのランタイムのインフラに他のトレース機構、たとえば tracepoint が乗っかってきた。ここでいうランタイムは、具体的には CPU スレッド単位のリングバッファ。Android の ATrace もこのバッファに便乗している。

コンパイラが関数単位で instrument するタイプのプロファイラは全然スケールしないので、ある規模以上のコードベースではふつう sampling-based profiler を使う。Linux も perf とかはサンプリングベースだったはず。

ただしそれとは別に、FTrace は "dynamic ftrace" という仕組みを追加した。カーネルのコードをプロファイラ有りでビルドしつつ、ランタイム関数の呼び出しは起動時に全部 nop で塗りつぶす。この塗りつぶしができるよう、呼び出し箇所の一覧を事前に作っておく。そして API で関数を指示すると nop の所にランタイムへの呼び出しを書き戻す。デバッガがやるようなことをカーネルの機能として提供している。たぶん今はこっちの方が主流っぽい。手元の Ubuntu ではふつうに有効になっている。Android はセキュリティのためカーネルを read only にしている都合で無効らしい。でも性能改善のドキュメントで紹介するくらいなら userdebug では有効にしておいてくれや・・・カーネルのリビルドとかやりかたわかんないよ・・・。

そんなかんじで Android からはカーネルの中に明示的にうめこまれた tracepoint たちとユーザ空間から書き込まれたデータしかトレースできない。まあいいんだけど。

Tracepoint のようにプログラマが手で足すにしろ Ftrace のようにコンパイラの助けを借りるにしろ、トレースの記録通知と通知を受けて何をするかは分離されており、イベントハンドラみたいのを(カーネル空間で)登録できる。リングバッファに書き込む、という部分はそのイベントハンドラの責務となっている様子。なかなか正しいデザインと言える。Brendan Greg の BPF 本では BPF をカーネルのイベントハンドラと表現していた。実際トレーシングのインフラがイベントハンドラを提供しているんだね。

Perfetto も自分でトレースフックのカーネルモジュールを書けば効率的にデータを読み出せそうなものだが、そんなカーネル依存のことはしてないだろうなあ。

リングバッファに話をもどすと、バッファには trace_pipe を読むとでてくるテキストが直接書き込まれているわけではなく、何らかのバイナリ列を書き込む。そして trace_pipe を解釈するタイミングでそのバイナリをテキストに整形する。なお ATrace はユーザ空間から任意のテキストを書き込める trace_marker という API を使っているので、じっさいバッファにテキストを書き込む。整形の仕組みをユーザ空間からもアクセスされてくれればよかったのに・・・。なお trace_marker_raw というバイナリ列を直に書き込む API もあることはある。

トレーシングというものの実装としてはだいぶがんばっている例の一つだと思うので、参考のためにもうちょっとコードを読んでみたいもんです。そのうち。