rust
tail

Rust で tail コマンドを実装してみた

キッカケ

ある日、急に Rust を使ってみたくなって、何か簡単に実装できるもので、かつ実用的になりそうなものを考えてみたところ、tail コマンドを実装することにしてみた。

C 言語での実装を参考に、ロジックは出来るだけそのまま利用して実装した。

実装環境

$ cargo -V
cargo 0.24.0 (45043115c 2017-12-05)
$ rustc -V
rustc 1.23.0 (766bd11c8 2018-01-01)

実装結果

rtail github

Usage: rtail [options] FILE

Options:
    -n NUMS             number of lines
    -h                  print help

まだ、借用チェッカーとの激しい戦いは収まらず、書きたい様に書けている感じが全然しない。
コンパイラに乗せられてコード書いてる感じ。

現状は初期実装のレベルで、最終的には標準入力からの処理と -f のフォロー処理は実装したいと思っている。

個人的な普段利用は macOS だが、仕事用 PC の Windows マシン用に書いている。
※まだ Windows での動作は未確認。

手元にあった、12 万行弱のログファイルを使ってベンチしてみた

  • tail (C 言語)
$ time tail -n 10000 120000.log
tail -n 10000 120000.log  0.06s user 0.02s system 30% cpu 0.251 total
  • rtail (Rust/debug)
$ time ./target/debug/rtail -n 10000 120000.log
./target/debug/rtail -n 10000 120000.log  0.63s user 1.15s system 90% cpu 1.979 total
  • rtail (Rust/release)
$ time ./target/release/rtail -n 10000 120000.log
./target/release/rtail -n 10000 120000.log  0.26s user 1.14s system 87% cpu 1.591 total

遅い。。遅過ぎる。
多分自分の実装が悪い。。はず。

追記(速度改善に取り組んだ 2018/01/28 1:10)

遅い原因は、何となく tail 結果表示バイト位置を決めるループにあるんだろうと考えていたが、実際その通りで、ファイルの読み取り位置を seek するバイト配列のバッファサイズを大きめ( 1024 バイト)にとってやったら劇的に速度改善した。

  • rtail (Rust/debug improved)
$ time ./target/debug/rtail -n 10000 120000.log
./target/debug/rtail -n 10000 120000.log  0.09s user 0.01s system 70% cpu 0.132 total
  • rtail (Rust/release improved)
$ time ./target/release/rtail -n 10000 120000.log
./target/release/rtail -n 10000 120000.log  0.00s user 0.01s system 7% cpu 0.205 total

C 実装並かそれ以上に速度が出ていて、いい感じ。ステキ。
バッファサイズを 1024 バイトにしたのは結構適当で、バッファサイズを 4096 バイトあたりまで試してみたが、これ以上の目立った速度改善は見られなかったので、1024 に留めておいた。

追記(標準入力からの処理を追加した 2018/01/29 23:30)

これと言って書くこともないのだが、機能追加したので追記しておく。
処理自体は結構バタ臭いというか泥臭いことをしているが、パフォーマンスチェックしたところ、上記の改善版と大きく変わらない速度が出ていたので、良しとする。

追記(ファイルの監視追加読み込みを実装した 2018/02/03 17:00)

-f のオプションで動くファイル監視をして追加分を表示する機能を実装した。
マルチプラットフォームでそのあたりをさくっとやってくれる crate(passcod/notify) を見つけたので、それを使えばあっさりと実装できた。

ただ、今のところ、狙い通りに動くのは linux でのみ。
macos と windows では、以下の通り。

  • macos

一部のケースでしかうまく動作しない。具体的に言うと /var/log/wifi.log などを監視している状態で Wifi の状態が変わっても何も読み込みが行われなかった。(適当なテキストファイルだとちゃんと動作する)
notify の実装を見てみると、macos の場合、FSEvents を使ってファイル監視しているようだ。
試しに homebrew で fswatch をインストールし、モニタリング方法を fsevents_monitor と
kqueue_monitor で切り替えてみたところ、kqueue_monitor でのみファイルの変更通知が来た。

FSEvents の仕様上、通知しないパターンがあるようだ。残念ながら、そういう記述がある箇所は見つけきれなかった。
notify の isuue にも kqueue の実装が入っていたので、実装されるのを待とうと思う。

  • windows

macos のケースと同じで、別プロセスが掴んでいるファイルの追加読み込みが行えない。
tail を流した状態で、そのファイルを掴んでいるプロセスを落とすと一気に読み込みが行われる。

マルチプラットフォームのアプリケーションって難しい。