\time

昔読んだ本に、『カッコウはコンピューターに卵を産む』ってのがあった。確かカリフォーニアに住む天文学者が、クラッカーを追い詰めた記録を、小説にしたものだった。

上から回って来る、コンピュータの使用請求書が、自分で管理してるデータより僅かに多かった。なんで? コンピュータが間違えるはず無いしな。そこから長い長い追跡劇が始まる。

今のコンピュータだと、そんな機能が入っているのかな? スパコンなんかだと使い放題って訳にもいかないだろうから、歯止めとして、そういう機構が入っていて、割り当てられた分だけ使うと、以後は使えなくなるとかなってるのかな?

ちょいと調べてみる。えーと、アカウント(会計)かな?

NAME
     ac - connect time accounting

/var/log/wtmpから引っ張り出した、接続時間。

ob6$ ac -d sakae
Aug  1  total        3.84
Aug  2  total        2.14
Aug  3  total        3.76
Aug  4  total        3.26
 :

で、他のコマンドとして、saなんてもの紹介された。acctonで、会計データの収録を始める。 溜まったデータをsaで確認出来るとな。

           avio   Average number of I/O operations per execution.
           cp     Sum of user and system time, in minutes.
           cpu    Same as cp.
           k      CPU time averaged core usage, in 1k units.
           k*sec  CPU storage integral, in 1k-core seconds.
           re     Real time, in minutes.
           s      System time, in minutes.
           tio    Total number of I/O operations.
           u      User time, in minutes.

acより格段に細かいデータが得られる。それに、どんなコマンドを投入したかも丸見えみたいだ。NetBSDの時代にこのコマンドが実装されたってなってた。時代で言うと、1993年か。

カッコウはコンピュータに卵を産むの読後録によると、事件が起きたのは1987年と言う事なんで、PC-UNIXってのはあり得ない。VAXが主役の時代だな、と懐かしんでおります。

WSLって速いの?

以前に入れた、Windows10で使えるリナちゃん、WSL。入れた時に体感的に遅いぞと思ったものだ。どのぐらい遅いか、一応暴いておく。例のファイル似てる度のアプリでね。

sakae@atom:/mnt/c/work$ time jot -b 'li.c li.c' 100 | xargs -L 1 ./aa.sh

real    0m9.727s
user    0m0.875s
sys     0m7.703s
sakae@atom:/mnt/c/work$ time jot -b 'li.c li.c' 100 | xargs -L 1 -P 4 ./aa.sh

real    0m6.611s
user    0m1.313s
sys     0m8.047s

Windows10だと、4つの石が素直に使えるはずなんで、4パラ運用したけど、むちゃ遅い。 topで1して、4個の石にプロセスが振り分けられている事は、確認出来るんだけどね。

Windows10のタスクマネージャーを起動して、CPUの利用率を見ると、100%になってる。これはもう暴走状態と言ってもいいぐらいの暴れっぷり。その割には大した仕事をしてないんだけどね。

それは、そうと、bashに備え付けのtimeじゃ、詳細分からんぞ。

sakae@atom:/mnt/c/work$ \time jot -b 'li.c li.c' 100 | xargs -L 1 -P 4 ./aa.sh
-bash: time: command not found
Runtime error (func=(main), adr=14): Divide by zero
Runtime error (func=(main), adr=14): Divide by zero
(standard_in) 1: syntax error
sakae@atom:/mnt/c/work$ /usr/bin/time jot -b 'li.c li.c' 100 | xargs -L 1 -P 4 ./aa.sh
-bash: /usr/bin/time: No such file or directory
Runtime error (func=(main), adr=14): Divide by zero
Runtime error (func=(main), adr=14): Divide by zero
(standard_in) 1: syntax error

内蔵コマンド(やalias)を無効にするには、コマンドの冒頭にバックスラッシュを置くのがしきたり。そうやったら、わけわかめな事を言われたので、フルパスで指定したら、そんなコマンド入ってないぞと言われた。Debianの最小構成だと、シス管ぽいコマンドは入っていない訳ね。

入れるのはやぶさかでないけど、timeってどう動いているか、最初に確認しとくかな。 そうじゃないと、ボーっと生きてんじゃないよと、NHKのチコちゃんに叱られる!しまいますからね。

まずはBSD系で

ob6$ \time -l emacs --version
GNU Emacs 25.3.1
         :
        0.29 real         0.00 user         0.20 sys
      3260  maximum resident set size
         0  average shared memory size
         0  average unshared data size
         0  average unshared stack size
       277  minor page faults
       444  major page faults
         0  swaps
       206  block input operations
         0  block output operations
         0  messages sent
         0  messages received
         0  signals received
       133  voluntary context switches
         2  involuntary context switches

へぇー、Net系のやり取りも把握してるんだ。ならば、

ob6$ \time -l curl -O http://space.geocities.jp/hamesspam
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3176  100  3176    0     0  28106      0 --:--:-- --:--:-- --:--:-- 28872
        0.18 real         0.00 user         0.05 sys
      3996  maximum resident set size
         0  average shared memory size
         0  average unshared data size
         0  average unshared stack size
       781  minor page faults
       148  major page faults
         0  swaps
        36  block input operations
         6  block output operations
         3  messages sent
         3  messages received
         0  signals received
        39  voluntary context switches
         5  involuntary context switches

3 messages って、パケット数の事かな?

Ubuntでは、どうよ?

sakae@usvr:/tmp$ cat x
jot -b 'li.c li.c' 100 | xargs -L 1 -P 4 ./aa.shsakae@usvr:/tmp
sakae@usvr:/tmp$ \time -v sh x
        Command being timed: "sh x"
        User time (seconds): 1.16                            (1.29)
        System time (seconds): 0.32                          (8.95)
        Percent of CPU this job got: 196%                    (148%)
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.75 (0:06.88)
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 2284              (868)
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 166126        (405124)
        Voluntary context switches: 5918                      (0)
        Involuntary context switches: 3269                    (0)
        Swaps: 0
        File system inputs: 0
        File system outputs: 0
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096                               (4096)
        Exit status: 0

注) 右側にカッコ付で表示したデータは、WSLの結果。

実時間は、0.75秒しかかかっていない。WSLでは、6.88秒って事だから、どんだけ遅いんや! 実用にしようったって、無理な相談だな。

メモリーが2284K使われた。I/Oに伴うページフォルトは発生してない。それ以外のページフォルトが166126回発生した。

自発的なコンテキストスイッチが5918回発生。これは、処理するデータが流れてこなかったんで、制御を他(のプロセス)に回すように申告されたって事だな。

3269回制御が取り上げられている。プロセスが調子よく実行されていたけど、その回の時間割り当て(通常は10ms)を超過したんで、他のプロセスに強制的に切り替えられたとな。

さあさあ、WSLの駄目さ加減を考察する時間です。

遅いのは、システム時間です。これが諸悪の根源です。WSLの限界を示しています。システム時間って事は、カーネル内で費やされた時間。Debian側から見れば、システムコールの処理がめちゃくちゃかかっている事になる。

Windows側がDebianのシステムコールを肩代わりして実行してるから。肩代わりって事は、システムコールをWindowsのシステムコール(そう言うのかは知らないけど)に変換して、Windowsで実行。その結果をDebian側に返す。これに手間がかかっているんだ。

そして有志の実験によれば、WSLが扱うファイル群が、WSLの領域にあるか、/mnt/c/xxx側に 有るかによって、スピードが数倍違うとの事。勿論遅い方は、/mntを介してのアクセス。ntfsのファイルシステムを一度ext4に翻訳するという無茶ぶりを強いられるからね。

でも、これってWSL側のキャッシュに載ってしまえば、関係無いんと違うかな。オイラーの例だと、十分にキャッシュに載ってると思うんだけどな。それでも遅いって事は、そんなの有難がって使うなって事だな。緊急避難的に使うのはしょうがないけど。

コンテキストスイッチが一切発生していない(ように見える)けど、これってどうなのよ。それじゃ、debian側が動作出来ないはずだけどな。そこまでの実装(真似)は、諦めているのかな?

どんな風にして、これらのデータを採取してるか、ソース嫁だな。

apt source time

大事な事だから、表題にしときましたからね。(/etc/apt/sources.listで、sourceを有効にしておく事)

色々なファイルが有るけど、見る(読む)のは、time.cだけで十分。

  pid = fork ();                /* Run CMD as child process.  */
  if (pid < 0)
    error (1, errno, "cannot fork");
  else if (pid == 0)
    {                           /* If child.  */
      /* Don't cast execvp arguments; that causes errors on some systems,
         versus merely warnings if the cast is left off.  */
      execvp (cmd[0], cmd);
      error (0, errno, "cannot run %s", cmd[0]);
      _exit (errno == ENOENT ? 127 : 126);
    }

forkしてexecveするのは、Unixの定番処理。ってか、これしかない。親の方は、wait3で子が終了するのを待つ。wait3が無ければwaitで待つ。(configure時に決定) そして、wait3から復帰すると、子のリソース使用状況(上記結果の元データ)が得られるので、それを整形して表示。 このwaitの部分の詳細は、resuse.cに置いてある。

  if (resuse_end (pid, resp) == 0)
    error (1, errno, "error waiting for child process");

親の方の子終了待ち。子が終了すれば、結果がrespに入ってる。 resp構造体のメンバーが、コメント付きで列挙されてた。(一部を掲載)

*  E == elapsed real (wall clock) time in [hour:]min:sec
*  F == major page faults (required physical I/O) (ru_majflt)
*  K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
*  M == maximum resident set size in K (ru_maxrss)
*  P == percent of CPU this job got (total cpu time / elapsed time)
*  R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
*  S == system (kernel) time (seconds) (ru_stime)
*  U == user time (seconds) (ru_utime)
*  c == involuntary context switches (ru_nivcsw)
   e == elapsed real time in seconds
*  r == socket messages received (ru_msgrcv)
*  s == socket messages sent (ru_msgsnd)
   t == average resident set size in K (ru_idrss)
*  w == voluntary context switches (ru_nvcsw)
   (* means the tcsh time builtin also recognizes it)

コンテキスト情報の取得がWSLでなされていない疑惑。どんなに大変な作業が検証してみるかな。それには、カーネル側のソースに当たる必要が有る。

オイラーもOpenBSDにコンテキストスイッチしましょ。

その前に、折角ソースが有るんで、料理して味わってみましょ。

(gdb) b resuse_end
Breakpoint 1 at 0x21fb: file resuse.c, line 75.
(gdb) r sh x
Starting program: /tmp/a.out sh x

Breakpoint 1, resuse_end (pid=28781, resp=0x7fffffffe330) at resuse.c:75
75      {
(gdb) p *resp
$1 = {waitstatus = 0, ru = {ru_utime = {tv_sec = 140737354131216,
      tv_usec = 140737349515143}, ru_stime = {tv_sec = 0,
      tv_usec = 140737488348032}, {ru_maxrss = 140737488348048,
      __ru_maxrss_word = 140737488348048}, {ru_ixrss = 140737354132120,
      __ru_ixrss_word = 140737354132120}, {ru_idrss = 0, __ru_idrss_word = 0},
    {ru_isrss = 0, __ru_isrss_word = 0}, {ru_minflt = 140737488348064,
      __ru_minflt_word = 140737488348064}, {ru_majflt = 4294967295,
      __ru_majflt_word = 4294967295}, {ru_nswap = 0, __ru_nswap_word = 0}, {
      ru_inblock = 140737354113640, __ru_inblock_word = 140737354113640}, {
      ru_oublock = 140737354131216, __ru_oublock_word = 140737354131216}, {
      ru_msgsnd = 0, __ru_msgsnd_word = 0}, {ru_msgrcv = 0,
      __ru_msgrcv_word = 0}, {ru_nsignals = 0, __ru_nsignals_word = 0}, {
      ru_nvcsw = 1970169159, __ru_nvcsw_word = 1970169159}, {ru_nivcsw = 9,
      __ru_nivcsw_word = 9}}, start = {tv_sec = 1534580364, tv_usec = 487949},
  elapsed = {tv_sec = 15775231, tv_usec = 1}}

wait3を呼び出している関数にBPを置いて走らせてみた。そして結果を持ち帰る構造体の中身を確認。まだその中は埋められていないので、めちゃくちゃな値が入っている。

respの元を辿れば、mainの中で宣言された変数だから、その時にスタック上はどんな値になってたかは不明と言うか不定だ。

(gdb) finish
Run till exit from #0  resuse_end (pid=16034, resp=0x7fffffffe330)
    at resuse.c:75
0x0000555555555453 in run_command (resp=0x7fffffffe330, cmd=<optimized out>)
    at time.c:623
623       if (resuse_end (pid, resp) == 0)
Value returned is $2 = 1
(gdb) p *resp
$3 = {waitstatus = 0, ru = {ru_utime = {tv_sec = 1, tv_usec = 475242},
    ru_stime = {tv_sec = 0, tv_usec = 427560}, {ru_maxrss = 2164,
      __ru_maxrss_word = 2164}, {ru_ixrss = 0, __ru_ixrss_word = 0}, {
      ru_idrss = 0, __ru_idrss_word = 0}, {ru_isrss = 0, __ru_isrss_word = 0},
    {ru_minflt = 167166, __ru_minflt_word = 167166}, {ru_majflt = 0,
      __ru_majflt_word = 0}, {ru_nswap = 0, __ru_nswap_word = 0}, {
      ru_inblock = 0, __ru_inblock_word = 0}, {ru_oublock = 0,
      __ru_oublock_word = 0}, {ru_msgsnd = 0, __ru_msgsnd_word = 0}, {
      ru_msgrcv = 0, __ru_msgrcv_word = 0}, {ru_nsignals = 0,
      __ru_nsignals_word = 0}, {ru_nvcsw = 5908, __ru_nvcsw_word = 5908}, {
      ru_nivcsw = 3352, __ru_nivcsw_word = 3352}}, start = {
    tv_sec = 1534915576, tv_usec = 792506}, elapsed = {tv_sec = 24,
    tv_usec = 421744}}

関数を実行終了させてから、構造体の中身を確認。今度はそれなりの値が入ってるっぽい。

(gdb) c
Continuing.
1.47user 0.42system 0:24.42elapsed 7%CPU (0avgtext+0avgdata 8656maxresident)k
0inputs+0outputs (0major+167166minor)pagefaults 0swaps
[Inferior 1 (process 16030) exited normally]

timeコマンドに任せて、結果を印刷。

proc

4.4BSD オペレーティングシステムの設計と実装

プロセスグループ と フォアグランドプロセス

複数のプロセスが動く時(パイプ等)に、それらのプロセスはどう扱われるかと思って調べてみたよ。

/usr/bin/time in OpenBSD

        while (wait3(&status, 0, &ru) != pid)
                ;

親プロセス側(/usr/bin/time)が、子プロセスの終了を待つ。リナのtimeと違って、wait3の決め打ち。なんで3が付いているかと言うと、引数が3個のwaitだから。実に安易な名前付け。 (昔で言うなら、一番最初の子は、一郎、二番目は二郎みたいなもんだな。)

システムコールで扱ってるのは、引数の数が一番多い wait4 だったりする。コードは、kern_exit.c に置かれてた。終活だな。終活内容は、子のリソース使用状況構造体を解放する事。その為のwaitだったりする。ある意味、子が死ぬのを待ち続ける怖い親でもある。

親が先にいなくなると、この終活が出来ず、俗に言うゾンビになって、システムが停止するまで 居座る事になる。

ゾンビプロセスと孤児プロセス

子プロセスの活動状況は、ruって所に格納されて返る。statusには、子プロセスの終了ステータスが入っている。

問題と言うか注目は、ruの中身と言うか構造体名 rusage。

sys/resorce.sh

struct  rusage {
        struct timeval ru_utime;        /* user time used */
        struct timeval ru_stime;        /* system time used */
        long    ru_maxrss;              /* max resident set size */
#define ru_first        ru_ixrss
        long    ru_ixrss;               /* integral shared text memory size */
        long    ru_idrss;               /* integral unshared data " */
        long    ru_isrss;               /* integral unshared stack " */
        long    ru_minflt;              /* page reclaims */
        long    ru_majflt;              /* page faults */
        long    ru_nswap;               /* swaps */
        long    ru_inblock;             /* block input operations */
        long    ru_oublock;             /* block output operations */
        long    ru_msgsnd;              /* messages sent */
        long    ru_msgrcv;              /* messages received */
        long    ru_nsignals;            /* signals received */
        long    ru_nvcsw;               /* voluntary context switches */
        long    ru_nivcsw;              /* involuntary " */
#define ru_last         ru_nivcsw
};

accton, sa

冒頭に出て来た会計をNetBSDでやってみた。会計システムをonにする為の儀式がまず必要との事。manによると、

acctonコマンドをrootで実行しろ。引数は、会計データを保存するファイル。指定しなければ、/var/account/acctってファイルが使われる。次に、/etc/rc.confに、accounting=YESと書いておいて、rebootしろ。これで、会計データの収録が始まる。

lastcommで、どんなコマンドを実行したか、もれなく確認出来る。

nb8$ lastcomm
cron       -F      root     __         0.00 secs Thu Aug 30 06:10 (0:00:00.00)
sh         -       root     __         0.00 secs Thu Aug 30 06:10 (0:00:00.00)
atrun      -       root     __         0.00 secs Thu Aug 30 06:10 (0:00:00.00)
cron       -F      root     __         0.00 secs Thu Aug 30 06:00 (0:00:10.06)
sh         -       root     __         0.00 secs Thu Aug 30 06:00 (0:00:10.06)
newsyslog  -       root     __         0.02 secs Thu Aug 30 06:00 (0:00:10.05)
:
rm         -       root     pts/1      0.00 secs Thu Aug 30 05:51 (0:00:00.00)
calendar   -       root     pts/1      0.00 secs Thu Aug 30 05:51 (0:00:00.00)
sa         -       root     pts/1      0.00 secs Thu Aug 30 05:51 (0:00:00.00)

起動時の早い段階からログされてるよ。

nb8$ sa
     277       17.95re        0.02cp        0avio        0k
      24        0.02re        0.02cp        0avio        0k   ***other
     106        2.32re        0.00cp        0avio        0k   sh*
       2        0.20re        0.00cp        0avio        0k   sshd*
       5        4.10re        0.00cp        0avio        0k   lv
      22        3.99re        0.00cp        0avio        0k   sh
      :
nb8$ sa -m
root           212         0.02cpu            0tio           0k*sec
postfix          1         0.00cpu            0tio           0k*sec
sshd             1         0.00cpu            0tio           0k*sec
sakae           81         0.00cpu            0tio           0k*sec

これが請求書になるんだな。sshdとかpostfixなんていう公共財は誰に請求が行くのかな?

man 5 acctによると

 struct acct {
             char    ac_comm[10];    /* name of command */
             comp_t  ac_utime;       /* user time */
             comp_t  ac_stime;       /* system time */
             comp_t  ac_etime;       /* elapsed time */
             time_t  ac_btime;       /* starting time */
             uid_t   ac_uid;         /* user id */
             gid_t   ac_gid;         /* group id */
             short   ac_mem;         /* memory usage average */
             comp_t  ac_io;          /* count of IO blocks */
             dev_t   ac_tty;         /* controlling tty */
             char    ac_flag;        /* accounting flags */
     };

こんな形式で、ログデータが埋められているみたいですなあ。クラッカーだと、これを改竄して、自分への請求を減らしたりするんだな。そういうのは、クラッカーじゃなくてても、悪徳官僚がよくやる手口です。ログが膨大になるんで、多少間引いても、ばれないだろうって心理が働くに違いない。

etc

フリーソフトウェア徹底活用講座