ローファイ日記

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

bpftraceのJSONフォーマットの構造

色々あって確認をしたので書く。

github.com

attached_probes

アタッチしたprobeの数。通常のフォーマットで「Attaching 1 probe...」と出る内容。

$ sudo bpftrace -B full -f json -e 'kprobe:vfs_read { printf("Hello, world\n"); }'
{"type": "attached_probes", "data": {"probes": 1}}
...

printf

printf 関数での出力結果のデータ。

$ sudo bpftrace -B full -f json -e 'kprobe:vfs_read { printf("[%ld] Hello, world\n", nsecs); }' | head -5
{"type": "attached_probes", "data": {"probes": 1}}
{"type": "printf", "data": "[38374324977911] Hello, world\n"}
{"type": "printf", "data": "[38374325064127] Hello, world\n"}
{"type": "printf", "data": "[38374325364310] Hello, world\n"}
{"type": "printf", "data": "[38374325581025] Hello, world\n"}

map

count() など値が一つの集計関数で渡ってくる構造。

$ sudo bpftrace -B full -f json -e 'kprobe:vfs_read { @c[comm] = count() } interval:s:5 { exit(); }'
{"type": "attached_probes", "data": {"probes": 2}}
{"type": "map", "data": {"@c": {"systemd-journal": 1, "gmain": 2, "vminfo": 7, "docker-containe": 54, "dockerd": 54, "redis-server": 96}}}

jq にかけるとこう。変数ごとにさらにマップがある感じ。

{
  "type": "map",
  "data": {
    "@c": {
      "systemd-journal": 1,
      "gmain": 2,
      "vminfo": 7,
      "docker-containe": 54,
      "dockerd": 54,
      "redis-server": 96
    }
  }
}

hist

hist() で渡ってくる構造。

$ sudo bpftrace -B full -f json -e 'kretprobe:vfs_read { @c[comm] = hist(retval) } interval:s:5 { exit(); }'
{"type": "attached_probes", "data": {"probes": 2}}

でかいので整形後のものだけ。

{                   
  "type": "hist",   
  "data": {         
    "@c": {         
      "systemd-journal": [
        {           
          "min": 8,  
          "max": 15, 
          "count": 1
        }           
      ],            
      "gmain": [     
        {            
          "max": -1,
          "count": 1
        },
      //...
      ],
      "redis-server": [
        {
          "max": -1,
          "count": 49
        },
        {
          "min": 0,
          "max": 0,
          "count": 0
        },
        {
          "min": 1,
          "max": 1,
          "count": 0
        },
        {
          "min": 2,
          "max": 3,
          "count": 0
        },
        {
          "min": 4,
          "max": 7,
          "count": 0
        },
        {
          "min": 8,
          "max": 15,
          "count": 0
        },
        {
          "min": 16,
          "max": 31,
          "count": 0
        },
        {
          "min": 32,
          "max": 63,
          "count": 0
        },
        {
          "min": 64,
          "max": 127,
          "count": 0
        },
        {
          "min": 128,
          "max": 255,
          "count": 0
        },
        {
          "min": 256,
          "max": 511,
          "count": 49
        }
      ]
    }
  }
}

変数→comm→レンジごとのcountの配列。

ちなみに lhist でも同じ構造を得られる。

~$ sudo bpftrace -B full -f json -e 'kretprobe:vfs_read { @c[comm] = lhist(retval, 0, 1000, 100) } interval:s:5 { exit(); }'                                                                                                                                                                                                         
{"type": "attached_probes", "data": {"probes": 2}}
...
{
  "type": "hist",
  "data": {
    "@c": {
      "systemd-journal": [
        {
          "min": 0,
          "max": 99,
          "count": 1
        }
      ],
//...
      "redis-server": [
        {
          "max": -1,
          "count": 48
        },
        {
          "min": 0,
          "max": 99,
          "count": 0
        },
        {
          "min": 100,
          "max": 199,
          "count": 0
        },
        {
          "min": 200,
          "max": 299,
          "count": 0
        },
        {
          "min": 300,
          "max": 399,
          "count": 48
        }
      ]
    }
  }
}

stats

stats() で渡ってくる構造。

$ sudo bpftrace -B full -f json -e 'kretprobe:vfs_read { @c[comm] = stats(retval) } interval:s:5 { exit(); }'
{"type": "attached_probes", "data": {"probes": 2}}
{
  "type": "stats",
  "data": {
    "@c": {
      "systemd-journal": {
        "count": 1,
        "average": 8,
        "total": 8
      },
      "docker-containe": {
        "count": 46,
        "average": 10,
        "total": 484
      },
      "dockerd": {
        "count": 47,
        "average": 10,
        "total": 512
      },
      "redis-server": {
        "count": 96,
        "average": 152,
        "total": 14640
      },
      "vminfo": {
        "count": 7,
        "average": 329,
        "total": 2304
      }
    }
  }
}

ネストしない場合

@c[comm] などとせず @c で受けた場合ネストが一段解除されるようで、 type を見ながら構造を動的に判定していい感じに扱う必要があるかもしれない。

特別な key の扱い

kstack() みたいなものは改行入りのつながった文字列のキーになる。配列のキーにはならない...。

{
  "type": "map",
  "data": {
    "@c": {
      "\n    kretprobe_trampoline+0\n    __x64_sys_read+26\n    do_syscall_64+90\n    entry_SYSCALL_64_after_hwframe+68\n": 209
    }
  }
}

[pid, comm] みたいにすると普通に , で結合された一つの文字列になる。

{
  "type": "map",
  "data": {
    "@c": {
      "systemd-journal,490": 1,
      "gmain,1031": 2,
      "vminfo,1611": 7,
      "docker-containe,2658": 50,
      "dockerd,1070": 52,
      "redis-server,1080": 98
    }
  }
}

bpftrace で追いつかないような複雑なフォーマットで出力したい場合は BCC を使うべき、ということになっているが、そうは言っても10徳ナイフ的な bpftrace が便利なのと、JSONをうまく取り扱えば割と要件を満たせるパターンが多そうに思われた。何かに使えればいいですね。