DTrace

Table of Contents

DTrace

DTraceはSunが開発したプロダクト。Sunと言えばもう一つ遺産を残してくれて いた。ZFSね。Zは最後って意味を含んでいるから、色々なfsが出ては消えてき たリナにも、もちろん登場してる。 OpenZFS 2.2.5がリリース⁠⁠、Linux 6.9をサポート まあ、ZFSは金持ちの為のFSだから庶民には関係無い。

そんな訳で、 BSD界隈四方山話 記事一覧 Dtraceの記事が連載されたので追試してみる。 リナ方面でも、独自の実装が有るみたいだ。 eBPFに3日で入門した話が、入口っぽいけど、色々準備が必要そうなので手出 しはしない。

まずはサンプルで実行。

# dtrace -n 'proc:::exec-success { trace(curpsinfo->pr_psargs); }'
dtrace: description 'proc:::exec-success ' matched 1 probe
CPU     ID                    FUNCTION:NAME
  0  60677                none:exec-success   /bin/sh /usr/bin/man dtrace
  0  60677                none:exec-success   /sbin/sysctl -n hw.machine_arch
  0  60677                none:exec-success   /sbin/sysctl -n hw.machine
  0  60677                none:exec-success   /usr/bin/locale
  0  60677                none:exec-success   /usr/bin/gzcat /usr/share/man/man1/dtrace.1.gz
  0  60677                none:exec-success   head -1
  0  60677                none:exec-success   /usr/bin/gzcat /usr/share/man/man1/dtrace.1.gz
  0  60677                none:exec-success   mandoc -Tlint -Wunsupp
  0  60677                none:exec-success   /usr/bin/gzcat /usr/share/man/man1/dtrace.1.gz
  0  60677                none:exec-success   mandoc
  0  60677                none:exec-success   less
  0  60677                none:exec-success   /bin/sh -c /usr/libexec/atrun
  0  60677                none:exec-success   /usr/libexec/atrun
  0  60677                none:exec-success   /bin/sh -c /usr/libexec/atrun
  0  60677                none:exec-success   /usr/libexec/atrun
  0  60677                none:exec-success   ps awx
^C

execされたコマンドのヒストリー。manが色々なコマンドの元に実行されてる な。暫くすると、cronの片割れであるatrunも自動的に起動されてる。カーネ ルの活動状況が、exec系を通して眺められるんだな。こりゃ便利だわい。

# dtrace -n 'io:::start { @bytes = quantize(args[0]->bio_bcount); }'
dtrace: description 'io:::start ' matched 1 probe
^C

           value  ------------- Distribution ------------- count
              -1 |                                         0
               0 |@@@@@@@@@@@@@@@@@@@@@@@@@@@              126
               1 |                                         0
               :
             256 |                                         0
             512 |                                         2
            1024 |                                         0
            2048 |                                         0
            4096 |@@                                       11
            8192 |                                         1
           16384 |                                         0
           32768 |@@@@@@@@@@                               49
           65536 |                                         0

そしてこちらは、ファイルをどんな単位で読み出ししたかの統計情報。value 0 は、多分パイプ経由かなにかだろう。

他にも面白そうなサンプルが用意されてた。

sakae@fb:/usr/share/dtrace $ ls
blocking*       hotopen*        siftr*          tcpstate*
disklatency*    nfsattrstats*   tcpconn*        tcptrack*
disklatencycmd* nfsclienttime*  tcpdebug*       udptrack*

HandBook Chapter 27. DTrace 更にハンドブックにも解説が出てた。

with awk

どんなセンサーもとえプローブが埋めこまれているかリストしてみます。

# dtrace -l | head
   ID   PROVIDER            MODULE                          FUNCTION NAME
    1     dtrace                                                     BEGIN
    2     dtrace                                                     END
    3     dtrace                                                     ERROR
    4        fbt            kernel                camstatusentrycomp entry
    5        fbt            kernel                camstatusentrycomp return
    6        fbt            kernel            cam_compat_handle_0x19 entry
    7        fbt            kernel            cam_compat_handle_0x19 return
    8        fbt            kernel            cam_compat_handle_0x17 entry
    9        fbt            kernel            cam_compat_handle_0x17 return
dtrace: failed to write to <stdout>: Broken pipe
# dtrace -l | wc
   63368  315990 4812317

提供者、モジュール、関数と名前で、特定できる様になってるとな。

#!/bin/sh
awk '{cnt[$2]++ }
     END {for (i in cnt) { print i, cnt[i] } }
     '

ならば、awkの連想配列を利用して、集約してみましょう。$2で集約って事は、 プロバイダーを列挙するんだな。

# dtrace -l | ./cnt.awk | sort -nr -k2
fbt 60663
syscall 1152
dtmalloc 818
mac_framework 242
vfs 183
nfscl 172
lockstat 26
 :

その結果、fbtってのが断トツで、銀メダルはsyscallってなった。

# dtrace -l | ./cnt.awk | sort -nr -k2 | head
kernel 60479
freebsd 1152
vmci 255
vop 158
nfs4 84
opensolaris 71
hidbus 58
nfs3 46
usbhid 44
nfs2 34

今度は、$3でモジュール毎に集約してみた。

dwatch

dtraceを使い易いように間をshellで仲介したコマンドがFreeBSD12から登場し てる。

# dwatch -h
Illegal option -h
Usage: dwatch [-1defFmnPqRvVwxy] [-B num] [-E code] [-g group] [-j jail]
              [-k name] [-K num] [-N count] [-o file] [-O cmd] [-p pid]
              [-r regex] [-t test] [-T time] [-u user] [-X profile] [-z regex]
              probe[,...] [args ...]
       dwatch -l [-fmnPqy] [-r regex] [probe ...]
       dwatch -Q [-1qy] [-r regex]

大事なのは、-l と -Q だ。いわゆるどんなセンサーと言うかプローブの種類 の表示をするのが -lだ。-Qはそれを結合して便利に使用できるようにした、D 言語の案内(manではprofileと説明してる)。

# dwatch -l | wc
   62645   62645 2275696
# dwatch -l | head
PROVIDER:MODULE:FUNCTION:NAME
callout_execute:kernel:none:callout-end
callout_execute:kernel:none:callout-start
dtmalloc::$PIR:free
dtmalloc::$PIR:malloc
dtmalloc::80211crypto:free
dtmalloc::80211crypto:malloc
dtmalloc::80211dfs:free
dtmalloc::80211dfs:malloc
dtmalloc::80211mesh_gt:free
# dwatch -Q | wc
      44      86    2177
# dwatch -Q | head
PROFILES:
        chmod                     errno
        fchmodat                  io
        io-done                   io-start
        ip                        ip-receive
        ip-send                   kill
        lchmod                    nanosleep
        open                      openat
        proc                      proc-create
        proc-exec                 proc-exec-failure

こちらの実行は、dwatch -X proc のようにする。

-d

-d        Debug.  Send dtrace(1) script to stdout instead of executing.

面白いオプションが有るので試してみる。

# dwatch -d exec >dcode
# less dcode
#!/usr/sbin/dtrace -s
#pragma D option quiet
#pragma D option dynvarsize=16m
#pragma D option switchrate=10hz

int console;

dtrace:::BEGIN { console = 0 } /* probe ID 1 */

/*********************************************************/

proc:kernel:none:exec /* probe ID 2 */
{
        /*
         * Examine process, parent process, and grandparent process details
         */

        /******************* CURPROC *******************/

        this->proc = curthread->td_proc;
        this->uid0 = this->proc ? this->proc->p_ucred->cr_uid : -1;
        this->gid0 = this->proc ? this->proc->p_ucred->cr_rgid : -1;
        this->pid0 = this->proc ? this->proc->p_pid : -1;
        :
proc:kernel:none:exec /* probe ID 3 */
{
        /***********************************************/

        printf("%s%Y%s ",
                console ? "\033[32m" : "",
                walltimestamp,
                console ? "\033[39m" : "");

        /****************** EVENT_TAG ******************/

        printf("%d.%d %s[%d]: ",
                this->uid0, this->gid0, execname, this->pid0);

        /**************** EVENT_DETAILS ****************/

        printf("%s", this->args0);

        /***********************************************/

        printf("\n");

}

こんな風に翻訳されて、それがdtraceに渡るんだな。ならば、これを実行。

# chmod 755 dcode
# ./dcode
2024 Aug 18 08:12:25 1001.1001 sh[14102]: -sh
2024 Aug 18 08:12:25 1001.1001 sh[14103]: /bin/sh /usr/bin/man ls
2024 Aug 18 08:12:25 1001.1001 sh[14104]: /bin/sh /usr/bin/man ls
2024 Aug 18 08:12:25 1001.1001 sh[14105]: /bin/sh /usr/bin/man ls
 :

ふむ、しっかり日時が付いていて、ログの形式になってる。

# cat LOG | awk '{print $6, $7, $8}' | uniq | less

日時を除くなら、awkに喰わせればいいか。それとも、ソースを変更しちゃう のも手だな。

::open: /* probe ID 3 */
/execname == "date"/
{
        printf("%d.%d %s[%d]: ",
                this->uid0, this->gid0, execname, this->pid0);

        printf("%s", this->args0);

        printf("\n");
}

こんな風に、プローブするコマンドを特定な者に制限できる。

# ./dcode
1001.1001 date[14252]: date -r 12345
1001.1001 date[14252]: date -r 12345
1001.1001 date[14252]: date -r 12345
 :

openなプローブって、どんなの? そんな時は、下記のようにする。

# dwatch -l open
PROVIDER:MODULE:FUNCTION:NAME
nfscl:nfs4:open:done
nfscl:nfs4:open:start
syscall:freebsd:open:entry
syscall:freebsd:open:return

/usr/libexec/dwatch

profile用のスクリプトは、まとめて置いてある。どんなのがあるか、いちど 参照してみる。例として、多用しそうな、procを確認。

sakae@fb:/usr/libexec/dwatch $ ls proc*
proc                    proc-exec-success       proc-signal-discard
proc-create             proc-exit               proc-signal-send
proc-exec               proc-signal             proc-status
proc-exec-failure       proc-signal-clear

こんな系列も有るけど、真実はひとつならぬ、カメレオン・モードを実現する ためみたいです。

# $Title: dwatch(8) module for dtrace_proc(4) activity $

case "$PROFILE" in
proc)
        : ${PROBE:=$( echo \
                proc:::create, \
                proc:::exec, \
                proc:::exec-failure, \
                proc:::exec-success, \
                proc:::exit, \
                proc:::signal-clear, \
                proc:::signal-discard, \
                proc:::signal-send )}
        ;;
proc-signal)
  :
inline string probealias[string name] =
        name == "create" ?              "FORK" :
        name == "exec" ?                "EXEC" :
        name == "exec-failure" ?        "FAIL" :
        name == "exec-success" ?        "INIT" :
        name == "exit" ?                "EXIT" :
 :
proc:::create /* probe ID $(( $ID + 1 )) */
{${TRACE:+
        printf("<$(( $ID + 1 ))>");
}
        $( pproc -P _create "(struct proc *)args[0]" )

        /* details = "pid <pid of args[0]> -- <proc args of args[0]>" */
        this->details = strjoin(
                strjoin("pid ", lltostr(this->pid_create)),
                strjoin(" -- ", this->args_create));
}
 :

シェルスクリプトですねぇ。注目は冒頭付近のコメントにさりげなく案内され てる、 dtrace_proc(4) です。

# dwatch -X proc -o LOG
INFO Sourcing proc profile [found in /usr/libexec/dwatch]
INFO Watching 'proc:::create, proc:::exec, proc:::exec-failure, proc:::exec-success, proc:::exit, proc:::signal-clear, proc:::signal-discard, proc:::signal-send' ...
INFO Setting output: LOG
^C

manの動作を追跡しようと思っていたらcronの動作もキャプチャーしてくれて いたので、そちらに焦点をあててみる。近頃は、あちこちに監視カメラやらド ラレコがついていて、油断も隙もない世界になっちまった。1984年が現実です。

# cat LOG | cut -b22-
2.5 cron[14493]: EXEC /bin/sh
2.5 sh[14493]: INIT /bin/sh -c /usr/libexec/save-entropy
2.5 sh[14493]: EXEC /usr/libexec/save-entropy
2.5 sh[14493]: INIT /bin/sh /usr/libexec/save-entropy
2.5 sh[14493]: FORK pid 14496 -- /bin/sh /usr/libexec/save-entropy
:
2.5 sh[14493]: FORK pid 14498 -- /bin/sh /usr/libexec/save-entropy
2.5 sh[14498]: EXEC /bin/dd
2.5 dd[14498]: INIT dd if=/dev/random of=saved-entropy.8 bs=4096 count=1
2.5 dd[14498]: EXIT child exited
2.5 dd[14498]: SEND SIGCHLD[20] pid 14493 -- /bin/sh /usr/libexec/save-entropy
2.5 dd[14498]: DISCARD SIGCHLD[20] pid 14493 -- /bin/sh /usr/libexec/save-entropy
2.5 sh[14493]: FORK pid 14499 -- /bin/sh /usr/libexec/save-entropy
2.5 sh[14499]: EXEC /bin/chflags
2.5 chflags[14499]: INIT chflags nodump saved-entropy.8
2.5 chflags[14499]: EXIT child exited
2.5 chflags[14499]: SEND SIGCHLD[20] pid 14493 -- /bin/sh /usr/libexec/save-entropy
2.5 chflags[14499]: DISCARD SIGCHLD[20] pid 14493 -- /bin/sh /usr/libexec/save-entropy
2.5 sh[14493]: FORK pid 14500 -- /bin/sh /usr/libexec/save-entropy
2.5 sh[14500]: EXEC /usr/bin/fsync
2.5 fsync[14500]: INIT fsync saved-entropy.8 .
2.5 fsync[14500]: EXIT child exited
2.5 fsync[14500]: SEND SIGCHLD[20] pid 14493 -- /bin/sh /usr/libexec/save-entropy
2.5 fsync[14500]: DISCARD SIGCHLD[20] pid 14493 -- /bin/sh /usr/libexec/save-entropy

ログの冒頭の日時は、cutしました。冒頭の 2.5 はpid.gidです。翻訳すると operator.operatorになります。

カメレオン・モードって表現したのは、こんな事です。

# dwatch -X proc-exec-success -o LOG
INFO Sourcing proc-exec-success profile [found in /usr/libexec/dwatch]
INFO Watching 'proc:::exec-success' ...
INFO Setting output: LOG
^C

特定なプローブだけをログする、すなわちS/N比を向上させます。なお、同一 名のログだと追記になるんで注意。

1001.1001 sh[14595]: INIT /bin/sh /usr/bin/man ls
1001.1001 sysctl[14596]: INIT /sbin/sysctl -n hw.machine_arch
1001.1001 sysctl[14597]: INIT /sbin/sysctl -n hw.machine
1001.1001 locale[14598]: INIT /usr/bin/locale
1001.1001 gzcat[14600]: INIT /usr/bin/gzcat /usr/share/man/man1/ls.1.gz
1001.1001 head[14601]: INIT head -1
1001.1001 gzcat[14602]: INIT /usr/bin/gzcat /usr/share/man/man1/ls.1.gz
1001.1001 mandoc[14603]: INIT mandoc -Tlint -Wunsupp
1001.1001 gzcat[14604]: INIT /usr/bin/gzcat /usr/share/man/man1/ls.1.gz
1001.1001 mandoc[14605]: INIT mandoc
1001.1001 less[14606]: INIT less

man dtrace_xxx

dtrace_proc(4) なんてのを見付けてしまったものだから、他にも有るだろ うと推測。やっぱり有った。分類上は、デバイス若しくは、デバドラ扱い。ま あ、センサーと見做せるから、当然か。

sakae@fb:/usr/share/man/man4 $ ls dtrace*
dtrace_audit.4.gz       dtrace_proc.4.gz        dtrace_udp.4.gz
dtrace_io.4.gz          dtrace_sched.4.gz       dtrace_udplite.4.gz
dtrace_ip.4.gz          dtrace_sctp.4.gz
dtrace_lockstat.4.gz    dtrace_tcp.4.gz

src

sakae@fb:/usr/src/cddl/usr.sbin/dwatch $ ls
Makefile        dwatch*         dwatch.1        examples/       libexec/

dwatchのソースは、こんな所にあった。cddlってのは、ライセンス名。GNUラ イセンスとは犬猿の仲なんで、リナは指を咥えて▼みんちょうたい

save-entropy

ここで会ったが百年目、どんな奴だか見極めておく。源流は、 /usr/libexec/save-entropy

難しい名前が付いているけど、乱数を貯蔵しておく仕組みだ。

# ls -l /var/db/entropy/
total 64
-rw-------  1 operator  operator  4096 Aug 19 08:33 saved-entropy.1
-rw-------  1 operator  operator  4096 Aug 19 08:44 saved-entropy.2
-rw-------  1 operator  operator  4096 Aug 20 06:11 saved-entropy.3
-rw-------  1 operator  operator  4096 Aug 20 06:22 saved-entropy.4
-rw-------  1 operator  operator  4096 Aug 19 07:55 saved-entropy.5
-rw-------  1 operator  operator  4096 Aug 19 08:00 saved-entropy.6
-rw-------  1 operator  operator  4096 Aug 19 08:11 saved-entropy.7
-rw-------  1 operator  operator  4096 Aug 19 08:22 saved-entropy.8

4Kブロックの乱数データが8個用意されてて、それを11分間隔で更新してんだ な。で、それを誰が利用するねん? おあつらえの例がmanに掲載されてた。

-P        Enable probe matching the specified provider name.

ランダムを使う人は誰って質問をしてみる。多分sshなんてのは、良いお得意 様だと思うので、別端末で ssh localhostしてみた。

# dwatch -P random
INFO Watching 'random:::' ...
dtrace: buffer size lowered to 6m
dtrace: dynamic variable size lowered to 8m
2024 Aug 20 06:27:37 1001.1001 ssh[18918]: ssh localhost
2024 Aug 20 06:27:46 0.0 sshd[18923]: sshd: sakae [priv]
2024 Aug 20 06:27:49 1001.1001 sh[18924]: -sh
^C

# dwatch -l random
PROVIDER:MODULE:FUNCTION:NAME
fbt:kernel:random:entry

ビンゴでしたねぇ。で、そのプローブの正体は? それより先に、 save-entropyの動作だなあ。肝は、

# Save entropy to the selected slot.
chmod 600 "${save_file}" 2>/dev/null || :
dd if=/dev/random of="${save_file}" bs=${entropy_save_sz} count=1 2>/dev/null
chflags nodump "${save_file}" 2>/dev/null || :
fsync "${save_file}" "."

ソースは、//dev/randomとな。デバイスだからmanのセクション4だな。

To provide prompt access to the random device at boot time, FreeBSD
automatically persists some entropy data in /boot/entropy for the loader
to provide to the kernel.  Additional entropy is regularly saved in
/var/db/entropy.  This saved entropy is sufficient to unblock the random
device on devices with writeable media.

To see the current settings of the software random device, use the
command line:

      sysctl kern.random     

カーネルが用意した乱数装置を確認。

sakae@fb:~ $ sysctl kern.random
kern.random.fortuna.concurrent_read: 1
kern.random.fortuna.minpoolsize: 64
kern.random.rdrand.rdrand_independent_seed: 0
kern.random.use_chacha20_cipher: 1
kern.random.block_seeded_status: 0
kern.random.random_sources: 'Intel Secure Key RNG'
kern.random.harvest.mask_symbolic: PURE_RDRAND,[UMA],[FS_ATIME],SWI,INTERRUPT,NET_NG,[NET_ETHER],NET_TUN,MOUSE,KEYBOARD,ATTACH,CACHED
kern.random.harvest.mask_bin: 000000001000000111011111
kern.random.harvest.mask: 33247
kern.random.initial_seeding.disable_bypass_warnings: 0
kern.random.initial_seeding.arc4random_bypassed_before_seeding: 0
kern.random.initial_seeding.read_random_bypassed_before_seeding: 0
kern.random.initial_seeding.bypass_before_seeding: 1

kern.random.harvest.mask_symbolic の括弧付き以外が元データになるん か。割り込みとかネットとかマウスとかの動きは誰にも想像がつかないから、 良い種になるとな。

昔のコンピュータだと放射能のカウンターが生成するデータを利用してたらし い。そんな設備は無いという人は、ツェナー・ダイオードから出てくる、電子 なだれの雑音を、トランジスタで整形してディジタル値に変換して、それを使っ てたらしい。昔、その回路を眺めた事があるぞ。

README

霧笛荘夜話

病院とかでの待ち時間に読もおとして借りた、特大文字版の小説。だって、眼 鏡してマスクじゃ鬱陶しいですから。裸眼で読めるって条件です。どうせなら、 活字が明朝体より黒々のゴシック体がよかったんだけど。

浅田次郎の小説である。6部屋のアパートの住人それぞれの話が綴られている。 これだけで6話になる。7話目が全体を〆ていて、とっても良かった。


This year's Index

Home