Solaris 11.4 で追加となった Dtrace の fileops でファイル操作の追跡が容易に
はじめに
Solaris 11.4 から fileops というファイル操作のための Dtrace プロバイダが用意されました。
これによって、ファイル操作に関するデバッグや統計情報の収集などが楽になります。
Dtrace 自体は、通常のコマンドレベルではできない、細やかなプロセスやカーネルの動きをリアルタイムにトレースすることができます。
従来はシステムコールや構造体を知っていないと複雑なファイル操作のDtace作成は困難でした。
Solaris11.4 からはfileops というファイル操作に関するプロバイダが用意されたことで欲しい情報が比較的楽にトレースできるようになりました。
以前のDtrace と fileops の比較例
ファイル操作は従来のDtraceでもsyscallなどプローブポイントとすることでトレース可能です。
ただし、ファイル名などをトレースするには、構造体の中身を表示させるといった手間が必要でした。
次の例は、bash プロセスがファイルに書き込みを実施した場合に、ファイル名を表示する Dtrace です。
#!/usr/sbin/dtrace -qs
syscall::write:entry
/execname == "bash"/
{
this->filistp = curthread->t_procp->p_user.u_finfo.fi_list;
this->ufentryp = (uf_entry_t *)((uint64_t)this->filistp
+(uint64_t)arg0 * (uint64_t)sizeof (uf_entry_t));
this->filep = this->ufentryp->uf_file;
this->vnodep = this->filep != 0 ? this->filep->f_vnode : 0;
self->vpath = this->vnodep ? (this->vnodep->v_path != 0 ?
cleanpath(this->vnodep->v_path) : "<unknown>") : "<unknown>";
printf("write : %s\n", self->vpath );
}
上記をファイル保存して、実行権を与えて実行します。
# chmod +x w-old.d
# ./w-old.d
write : /export/home/test
write : /export/home/test
write : /export/home/test
fileops を使うと以下のように記載できます。
#!/usr/sbin/dtrace -qs
fileops:::write
/execname == "bash"/
{
printf("write : %s\n", args[0]->fi_pathname );
}
すごくシンプルに記載することができます!
fileops のプローブポイント
fileops で用意されたプローブポイントを確認してみます。
dtrace -l でプローブポイントを確認できます。
更に -P オプションでfileopsプロバイダのみプローブポイントを表示できます。
# dtrace -P fileops -l
ID PROVIDER MODULE FUNCTION NAME
14864 fileops genunix fop_remove unlink
14867 fileops genunix fop_rename rename
14890 fileops genunix fop_symlink symlink
15197 fileops genunix fop_open open
15198 fileops genunix fop_link link
15199 fileops genunix fop_read read
15253 fileops genunix fop_mkdir mkdir
15261 fileops genunix fop_close close
15263 fileops genunix fop_setattr chmod
15264 fileops genunix fop_setattr chown
15265 fileops genunix fop_setattr chgrp
15267 fileops genunix fop_write write
15269 fileops genunix fop_rmdir rmdir
15283 fileops genunix fop_readdir readdir
102258 fileops genunix fop_create create
fileops プロバイダでは、15個用意されています。
15種類のファイル操作をトレースできるということになります。
用意される引数(データ)
各プローブは args[0] から args[4] までの変数が用意され、それぞれ情報を保持しています。
args[0] はファイル情報、args[1] はオペレーションの時間情報、args[2] ~ args[4] はそれぞれプローブごとに用意される情報が異なっています。
これらの変数にアクセスすることで容易に関連情報を取得できます。
例えば、ファイルをクローズする close のプローブでオペレーションの待ち時間が args[1] に入っています。
ファイルクローズ時のオペレーションの待ち時間を表示したい場合には、trace(args[1]) とDtraceで記載するだけです。
fileops のプローブポイントと引数一覧
chmod
ファイルのアクセス権モードが変更されたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、新しいファイルモードが格納されています。
chown
ファイルの所有権が変更されたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、ファイルの新しい所有者のユーザーIDが格納されています。
chgrp
ファイルのグループ所有権が変更されたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、ファイルの新しい所有者のユーザーIDが格納されています。
close
開いているファイルが閉じられたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
create
ファイルが作成されたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、新しいファイルモードが格納されています。
link
ハードリンクが既存のファイルに作成されたときに起動するプローブ。
ファイルに対応するfileinfo_t構造体は、 args [0]によって指し示されます。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、新しいリンクに対応するfileinfo_tが格納されます。
mkdir
新しいディレクトリが作成されたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、新しいディレクトリモードが格納されています。
open
既存のファイルが開かれたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、open(2) フラグが含まれています。
open(2) を O_CREATフラグで実行し、ファイルが存在しない場合は、
createプローブがファイルの作成時に起動します。
read
開いているファイルから読み込みが行われたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、ファイルオフセットが格納されます。
args[3]は、要求されたバイト数が格納されます。
args[4]は、実際に読み取られたバイト数が格納されます。
readdir
ディレクトリが読み込まれたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、読み込まれたバイト数が格納されます。
rename
ファイルの名前が変更されたときに起動するプローブ。
args[0]は、元のファイル名に対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、新しいファイル名に対応する fileinfo_t 構造体をポイントしています。
rmdir
ディレクトリが削除されたときに起動するプローブ。
args[0]は、ディレクトリに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
symlink
ファイルへのシンボリックリンクが作成されたときに起動するプローブ。
args[0]は、ソースファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、シンボリックリンクのターゲットを表す文字列が格納されます。
unlink
既存のファイルへのリンクが破棄されたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
write
開いているファイルに書き込みが行われたときに起動するプローブ。
args[0]は、ファイルに対応する fileinfo_t 構造体をポイントしています。
args[1]は、オペレーションの待ち時間が格納されます。
args[2]は、ファイルオフセットが格納されます。。
args[3]は、要求されたバイト数が格納されます。
args[4]は、実際に書き込まれた実際のバイト数が格納されます。
args[0]の fileinfo_t 構造体は以下の通りです。
fileinfo_t 構造体
typedef struct fileinfo {
string fi_name; /* 名前 (basename of fi_pathname) */
string fi_dirname; /* ディレクトリ (dirname of fi_pathname) */
string fi_pathname; /* フルパス */
offset_t fi_offset; /* ファイル内のオフセット */
string fi_fs; /* ファイルシステム */
string fi_mount; /* ファイルシステムのマウントポイント */
} fileinfo_t;
fileops プロバイダの使用例
ファイルのアクセス権モードの変更をトレース
fileops:::chmod プローブは、ファイルのアクセス権モードの変更すると起動します。
以下のスクリプトは、chmod プローブが起動すると fileops:::chmod プローブのargs[0]~args[2]の値をすべて表示します。
以下のスクリプトを chmod.d として保存します。
fileops:::chmod
{
/* args[0] -- fileinfo_t */
printf("fi_name : %s \n" , args[0]->fi_name);
printf("fi_dirname : %s \n" , args[0]->fi_dirname);
printf("fi_pathname : %s \n" , args[0]->fi_pathname);
printf("fi_offset : %d \n" , args[0]->fi_offset);
printf("fi_fs : %s \n" , args[0]->fi_fs);
printf("fi_mount : %s \n", args[0]->fi_mount);
/* args[1] -- latency */
printf("latency : %d \n", args[1]);
/* args[2] -- mode_t */
printf("mode : %d%d%d%d \n",
(args[2] & 0xe00) >> 9,
(args[2] & 0x1c0) >> 6,
(args[2] & 0x38) >> 3,
(args[2] & 0x7) );
}
dtrace コマンドで -s オプションの後にファイルを指定して実行します。
# dtrace -qs fileops.d
別の端末でファイルのアクセス権限モードを chmod コマンドで変更します。
# chmod 755 /var/tmp/a
Dtraceの出力は以下の通り、chmod を行ったファイル情報とオペレーション時間
そして、新しく設定されたアクセス権限モード(0755)が表示されます。
# dtrace -qs fileops.d
fi_name : a
fi_dirname : /var/tmp
fi_pathname : /var/tmp/a
fi_offset : 0
fi_fs : zfs
fi_mount : /var/tmp
latency : 61226
mode : 0755
ファイルの削除とリネーム
fileops:::unlink,fileops:::rename
/args[0]->fi_pathname == "/var/tmp/a" /
{
printf("time : %Y \n" , walltimestamp );
printf("execname : %s \n" , execname );
printf("pid : %d \n" , pid );
printf("ppid : %d \n" , ppid );
printf("uid : %d \n" , uid );
}
# dtrace -qs r.d
別端末でファイルの削除
# rm /var/tmp/a
以下のように表示される。
# dtrace -qs r.d
time : 2018 Apr 23 23:00:00
execname : rm
pid : 10420
ppid : 1589
uid : 0