Expect - while文

ある期待する標準出力の応答が来るまでコマンドを繰り返し実行したい場合があります。

筆者は半導体エンジニアであるので、論理設計のシミュレーションや設計の妥当性確認(設計ルールに合っているかの確認)を実施するために高額なEDAツール(Electronic Design Automation)を使います。
また、高額なEDAツールと共に高性能なLINUXサーバーも必要となります。

これらのLINUXサーバーやEDAツールは大勢の半導体エンジニアでシェアして使うため、LSF と呼ばれるロードバランサー (IBMのものが有名) で適切なコンピュートマシンにJOBが割り振られ、コンピュートマシンの空きがない場合は、JOBが待たされる といった仕組みになっているのが一般的です。

待たされても JOBが実行されるなら良いのですが、実行されてもツールのライセンスが既に他のJOBで使い切られて無くなっていると、"No more license" などというメッセージが表示されて虚しくJOBが終了する場合があります。

すると、半導体エンジニアは、すかさずJOBを再投入することとなります。

本来ならJOBが投入されるコンピュートマシンの数と Licenseの数を同じになるように LSF で設定しておけば、"No more license" などというメッセージを見ることなく JOBが実行されるのですが、そのように上手く設定出来ない EDAツールがあったりするのです。

さて、このような JOBが待たされ、"No more license"によるJOB終了、そしてJOBの再投入、といったループを半導体エンジニアがやるのは非効率です。では 人間に代わって Expect にこのループを実行してもらいましょう。


いきなり 高額なEDAツールとLINUXサーバーを使って Expect のループを実行させるのは危険なので、「JOBが待たされ、"No more license"によるJOB終了」をエミュレートする Perl を以下のように準備しました。

license_check.pl

  1   #!/usr/bin/perl
  2  
  3   $job_id = int(rand 999999);
  4   print "Job <" . "$job_id" . "> is submitted to queue\n";
  5   sleep 1;
  6   print "<<Waiting for dispatch ...>>";
  7   sleep 5;
  8  
  9   $machine_id = int(rand 3500);
 10   print "<<Starting on susan" . "$machine_id" . ">> \n";
 11   sleep 5;
 12  
 13   $lc = int(rand 11);
 14   if ($lc < 8) {
 15     print "No more license \n";
 16   } else {
 17     print "Executing Job \n";
 18     for ($j = 0; $j < 3; $j++) {
 19       for ($i = 0; $i < 10; $i++) {
 20         print ".";
 21         sleep 1;
 22       }
 23       print "\n";
 24     }
 25     print "done \n";
 26   }
 27  

上の Perlプログラムでは ランダム関数 rand が使われています。

rand関数に 例えば 「10」を与えれば、「2.345...」「5.678...」のように 0 以上で 10 より小さい実数が生成されます。また、その乱数を自然数にするために int関数 を使っています。

上の Perlプログラムでは 13行目で 0〜10 の間の自然数を ランダムに与えられています。
そして 14行目にある通り、与えられた数字が 8 より小さければ "No more license" を表示して終了。それ以外なら JOBが実行されているかのように見せかけています。
つまり、80% 確率において "No more license" になり、JOBが実行されないことになります。


それでは次に Expect で作ったスクリプトですが、以下のようになります。

while_test.exp

  1   #!/usr/bin/expect
  2  
  3   set send_slow {2 .2}
  4   set timeout 10
  5  
  6   proc expect_prompt { } {
  7     expect {
  8       "$ " { }
  9       timeout { exit }
 10     }
 11   }
 12  
 13   proc re_run { } {
 14     expect "$ "
 15     send -s -- "./license_check.pl\r"
 16   }
 17  
 18   spawn bash
 19   log_file -noappend while_test.log
 20   expect_prompt
 21   send -s -- "cd /home/user/Lab/Expect/while_test\r"
 22   expect_prompt
 23   set timeout -1
 24   send -s -- "./license_check.pl\r"
 25   while 1 {
 26     expect {
 27       "Executing Job" break
 28       "No more license"
 29     }
 30     re_run
 31   }
 32  
 33   interact
 34  

上のスクリプトで見て分かる通り、25行目から while 1 によるループがベタに書かれています。
そして、JOBが実行された証である "Executing Job" という出力とマッチすると、break によって while 1 ループから抜け出します。

この単純な break によってループから抜け出す方法が バグもなく安全な方法かもしれません。

この whileループの他に重要な仕掛けとして、23行目の set timeout -1 があります。
これは、パターンマッチまでの時間を無制限にするものです。LSFのJOB待ちが長いことが度々ありますので、忘れてはならない設定です。

もう一つ大切な設定は、19行目の log_file です。人が見ていない間の状況を log で確認することが出来ます。


while 1 で無限ループになっているのが危ういと感じる場合は for文 を使うと良いですね。
for文 にするには 25行目を以下のように書き換えるだけで済みます。

for_test.exp

 25   for {set i 0} {$i < 3} {incr i} {

以上の ループをテストした結果は以下のようになります。

while_test.log

$ cd /home/kena/Lab/Expect/test
$ ./license_check.pl
Job <858053> is submitted to queue
<<Waiting for dispatch ...>>
<<Starting on susan3325>>
No more license
$ ./license_check.pl
Job <239081> is submitted to queue
<<Waiting for dispatch ...>>
<<Starting on susan3415>>
No more license
$ ./license_check.pl
Job <971154> is submitted to queue
<<Waiting for dispatch ...>>
<<Starting on susan328>>
No more license
$ ./license_check.pl
Job <148786> is submitted to queue
<<Waiting for dispatch ...>>
<<Starting on susan1987>>
Executing Job
..........
..........
..........
done
$ exit
exit