Solaris11.4 Dtrace の新機能 fileops プロバイダを使ってみた

# Solaris 11.4 で追加となった Dtrace の fileops でファイル操作の追跡が容易に


## はじめに

Solaris 11.4 から fileops というファイル操作のための Dtrace プロバイダが用意されました。
これによって、ファイル操作に関するデバッグや統計情報の収集などが楽になります。

Dtrace 自体は、通常のコマンドレベルではできない、細やかなプロセスやカーネルの動きをリアルタイムにトレースすることができます。

従来はシステムコールや構造体を知っていないと複雑なファイル操作のDtace作成は困難でした。
Solaris11.4 からはfileops というファイル操作に関するプロバイダが用意されたことで欲しい情報が比較的楽にトレースできるようになりました。



## 以前のDtrace と fileops の比較例

ファイル操作は従来のDtraceでもsyscallなどプローブポイントとすることでトレース可能です。
ただし、ファイル名などをトレースするには、構造体の中身を表示させるといった手間が必要でした。

次の例は、bash プロセスがファイルに書き込みを実施した場合に、ファイル名を表示する Dtrace です。

```dtrace:w-old.d
#!/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 を使うと以下のように記載できます。

```dtrace:w-new.d
#!/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 構造体 

```c
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 として保存します。

```dtrace: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
```


## ファイルの削除とリネーム

```dtrace:r.d
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
```