ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

eBPF for Rubyist 現状確認(2) - BPFQL の紹介

udzura.hatenablog.jp

こちらの続きです。第2回は拙作「BPFQL」を紹介したく。RbBCCの実例として2つのプロトタイプを作ったうちの1つです。

まず、汎用トレースツール bpftrace の話

前回RbBCCを紹介しましたが、一般にRbBCCやBCCは「サーバの特定の状況をトレースするために」使われます。たとえばdisk I/Oのレイテンシが知りたい、ページフォールトの状況が知りたい、TCPパケットの受信状況は、などなど...。

go-vargo.hatenablog.com

「どういう状況の時に」BCC製の「どのツールが」使われるかは上記のvargoさんの記事にも一部紹介されています。

一方で、私たちの運用しているサーバはどういう状況に遭遇するか簡単には予測できない面もあります。そういう場合に、初動などで様々な値を汎用的に取れるようにしているツールとしてbpftraceが存在し、tracepointやkprobe等の知識があればこのコマンドだけで非常にたくさんの情報が取得できます。

github.com

独自の外部DSL、bpftrace言語

で、bpftraceは以下のような、DTrace言語に似た外部DSLを利用するわけですが。

BEGIN
{
    printf("Tracing block device I/O... Hit Ctrl-C to end.\n");
}

kprobe:blk_account_io_start
{
    @start[arg0] = nsecs;
}

kprobe:blk_account_io_done
/@start[arg0]/
{
    @usecs = hist((nsecs - @start[arg0]) / 1000);
    delete(@start[arg0]);
}

END
{
    clear(@start);
}

以下より。

github.com

何回か素振りで書いていると結構、

  • 冗長な時がある(正直pid、comm、タイムスタンプは毎回表示したい)
  • hist() ? count() ? 集計関数に癖がある...
  • 結構定型的な表現が多い(上記のようなレイテンシの計測など)

ということでもっと宣言的にシュッとかけないかなあ? という思いから作ってみたのがBPFQLでした。

YAML/RubyDSLで宣言的に欲しい値をトレースする BPFQL

論より証拠というか、以下がBPFQLのスクリプトです。

BPFQL do
  select "ts", "comm", "pid", "nr_sector"
  from "tracepoint:block:block_rq_complete"
end

markdownでも ruby でシンタクスハイライトできる、完全なRubyの内部DSLです。 block:block_rq_complete はブロックデバイスへのI/O要求の完了に関するtracepointです。

これをあるターミナルでbpfqlコマンドに渡すと、フィールドが出てきてブロックします。

bpfql blockreq.rb
TS                 COMM             PID    NR_SECTOR

しばらく待つとマシン内でのブロックI/Oが実行されたことにより影響を受けた「セクタ数」が表示されます。

# bpfql blockreq.rb      
TS                 COMM             PID    NR_SECTOR
0.000000000        swapper/0        0      0
0.001375301        swapper/1        0      0
0.001369696        swapper/2        0      64
0.001528832        bpfql            2405   8
0.001593498        gmain            686    0
0.002032037        swapper/1        0      0
1.004273509        swapper/0        0      0
1.005479007        swapper/1        0      0
1.005726634        swapper/0        0      0
1.006048318        swapper/1        0      0
1.034544968        swapper/0        0      24
...

今度はスクリプトを以下のように変更しましょう。今回は、しばらく待っても表示されるレコードが増えないと思います。

BPFQL do
  select "ts", "comm", "pid", "nr_sector"
  from "tracepoint:block:block_rq_complete"
  where "comm", is: "dd"
end

別のターミナルで以下のコマンドを動かすと、

sudo dd if=/dev/sda1 of=/dev/null bs=1M

dd による読み込みセクタ数がすごい勢いで出てきます。このように「 where 句」での絞り込みは実装しているため、例えば特定のコマンドのみ追いかけることが可能です。ddコマンドの bs の値をいろいろに変えて変化を見ると面白いかもしれません。

0.518328203        dd               2523   1024
0.518666974        dd               2523   1024
0.524834622        dd               2523   1024
0.526855612        dd               2523   1024
0.531083256        dd               2523   1024
0.532084090        dd               2523   1024
0.535336335        dd               2523   1024
0.536024702        dd               2523   1024
0.551834078        dd               2523   1024
0.555861535        dd               2523   1024
0.556636709        dd               2523   1024
0.560012807        dd               2523   1024
0.561375370        dd               2523   1024
0.565537604        dd               2523   1024
0.566284519        dd               2523   1024
0.572071917        dd               2523   1024
0.576701969        dd               2523   2048
0.581984174        dd               2523   1024
0.582651214        dd               2523   1024
0.586457406        dd               2523   1024
...

Caveats

さて、RubyDSLという「いかにも直感的な見た目」を生かしたツールで、なんとなく書けそうな感じがする、これはすごい! みたいな印象を持たれたかもしれませんが...。

BPFQL はまだ絶賛開発中の段階で、たとえば:

  • tracepoint しかトレースできません
  • comm 以外の char[] のフィールドを表示できません
  • hist(), count() ほか集計関数は使えません
  • レイテンシがまだ見られません
  • エラーハンドリングなどもイマイチ...

と言った制約があり、PoCの段階を出ていません... -_-b

一方で、これらの未実装の機能を一通り実装すれば、それなりに使いでがあるツールに化ける気もしています。Contributionを歓迎します...。


ということで、せっかくRubyBCCを使えるということでそれっぽいツールを一つ紹介しました。

BPFQL も前回の rbbcc と同じく、

gem install bpfql

でインストールできます。先述の通りバグも多いのですが... お試しください。