ジョブネットとコントローラの自作2
ジョブネットコントローラの自作第二弾。 第一弾は awk を駆使して実現したが、 awk はコードが見づらく癖もあるため、ジョブコントロールに向かいないと感じていた。 また、ジョブの完了状態チェックをステータスファイルを用いて簡単化するため、今回は基本的にシェルスクリプトだけで実現した。
例として、図のようなフローで J01 からJ09 までのジョブを今回作成するジョブコントローラで実行してみる。
仕様
実行ファイル : jobctl
引数 : ジョブ格納ディレクトリ
ジョブフロー設定ファイル jobnet.csv に従ってジョブを実行する。 jobflow.csv の第1列は実行対象ジョブ、第2列目以降は依存ジョブが記載されている。 実行対象ジョブは依存ジョブの完了後に実行される。 このような行がリストされることで、ジョブフローが形成される。
実行対象ジョブは可能な限り並行稼働して、無駄な待機時間をなくす。 ジョブの実行ログファイルがログディレクトリに出力される。 実行対象ジョブは、任意の一つのディレクトリにまとめて存在する必要がある。
戻り値は、実行したジョブの終了ステータスに依存しない。 実行したジョブが異常終了した場合、他に実行中のジョブがあれば、それが完了してからジョブコントローラを終了する。
ディレクトリ構成とファイル
このジョブコントロールシステムは、親ジョブを直接コントロールする。 親ジョブとは、コントロールしたいジョブを内包(ラッピング)したシェルスクリプト。 上図の dir1 について、特に明記していなくてもジョブは親ジョブのことを指す。
コントロールしたいジョブは、任意の一つのディレクトリ(上図の dir2)にまとめて格納されていること。 ジョブは、任意の実行形式ファイル(シェルスクリプト等)。
「任意の〜」と記載されていないディレクトリやファイルは、このジョブコントロールシステム本体であり、変更は許されない。
親ジョブ作成ツール ( makeParent )
makeParent(親ジョブ作成ツール)は、 jobnet.csv とtemplate をもとに親ジョブを作成する。 template には、以下の処理が記述されている。
- ジョブをラッピングして実行
- 1.のジョブ実行の前後で、開始/終了時刻をログ出力
- status に実行状態ファイルの出力
makeParent のコード例
1#!/bin/bash
2
3# jobnet.csv の実行対象ジョブに対して、それをラッピングして実行する親ジョブ>を job ディレクトリに作成する
4
5err(){
6 STATUS=$?
7 echo "単純なコマンドの実行でエラーが発生しました"
8 echo "${BASH_SOURCE##*/}: $BASH_LINENO: $BASH_COMMAND: $STATUS"
9 exit 1
10}
11trap err ERR
12
13# 既存の親ジョブを削除
14rm -f job/P_* >& /dev/null
15
16# 親ジョブの作成
17awk -F, '!/^#|^$/{print $1}' <(gsed -E 's/( |\t)//g' jobflow.csv.tmp) |
18while read; do
19 cp -p job/template job/P_$REPLY
20 chmod 755 job/P_$REPLY
21done
22
23exit 0
template のコード例
1#!/bin/bash
2
3# 自分自身のファイル名と、実行するジョブ名を取得(ex. P_ABC -> ABC)
4p_jobname=${BASH_SOURCE##*/}
5jobname=${p_jobname:2}
6
7# メッセージ出力先をログファイルに向ける
8exec > $logdir/$p_jobname.log 2>&1
9
10# startファイル作成
11touch $stsdir/$p_jobname.start
12echo "$jobname 開始 : $(date)"
13
14# ジョブの実行
15$jobdir/$jobname # jobdirはjobctl実行時の引数値(exportされている)
16if [ $? -eq 0 ];then
17 echo "$jobname 正常終了 : $(date)"
18 SUF="end"
19else
20 echo "$jobname 異常終了 : $(date)"
21 SUF="err"
22fi
23
24# end または err ファイル作成
25touch $stsdir/$p_jobname.$SUF
ジョブフロー定義ファイル ( jobflow.csv )
実行順序を規定する jobflow.csv (ジョブフロー定義ファイル)をあらかじめ作成する。 下記のように、左端に実行するジョブ、その右側にカンマ区切りで依存ジョブ(実行するために完了しておくべきジョブ)を記載する。 なお、 jobctl の処理により、 # より右側はコメントと見做され、空行は無視される。
1# シャープ(#)の右側はコメント扱いとなる
2# 空行は無視される
3
4J01
5J02
6J03
7J04
8
9# J05はJ01,J02,J03が終わってから実行
10J05,J01,J02,J03
11
12# J06はJ03,J04が終わってから実行
13J06,J03,J04
14
15J07
16
17# J08はJ05が終わってから実行
18J08,J05
19
20# J09はJ06,J07が終わってから実行
21J09,J06,J07
ジョブコントローラ ( jobctl )
処理の流れ
- jobflow.csv の整形
- ステータスファイルのクリア
- メインループ
- 全ジョブ完了チェック。 完了していたら終了する。
- ジョブの実行。 jobflow.csv を走査し、依存ジョブが全て正常終了していることを確認後、ジョブを実行する。
コード例を示す。 チェック処理を端折っている(コードが長くなって理解しづらくなるため)。 引数チェック(引数がディレクトリとして存在するか)、 jobflow.csv の存在チェック、 jobflow.csv 内の各ジョブファイルの存在チェックは必要だろう。
さらには、対話型にしてジョブが異常終了した場合、ジョブフローの最初から開始するか、異常終了ジョブから開始するか、異常終了ジョブをスキップするかを選択させるメニューを表示するなど、改良することも可能だ。
1#!/bin/bash
2pwd=$PWD
3p_jobdir=$pwd/job
4stsdir=$pwd/status; export stsdir
5logdir=$pwd/log; export logdir
6
7# 共通の終了処理
8finish(){
9 cd $pwd
10 wait # 実行中のジョブがあれば待機する(このコードは不要の判断もありうる)
11 if [ -f jobflow.csv.tmp ];then rm jobflow.csv.tmp; fi
12}
13
14# 引数のディレクトリパスを絶対パスに置換
15jobdir=$(realpath $1); export jobdir
16
17# 初期処理:csvの整形(以下処理順序に注意)
18sed 's/#.*//' jobflow.csv > jobflow.csv.tmp # コメント文字以降を除去
19gsed -i -E 's/( |\t)//g' jobflow.csv.tmp # 空白・タブを除去
20gsed -i '/^$/d' jobflow.csv.tmp # 空行を除去
21
22# 初期処理:親ジョブの作成
23./makeParent
24if [ $? -ne 0 ];then
25 echo "makeParent error"
26 exit 1
27fi
28
29# 初期処理:ステータスファイルのクリア
30(cd $stsdir; rm -f *.start *.end *.err 2>/dev/null)
31(cd $logdir; rm -f *.log)
32
33# 関数:全ジョブ完了チェック
34completeCheck(){
35 # completeFlg: 0(未完了ジョブあり), 1(全て完了), 99(異常終了ジョブあり)
36 # errファイルの存在チェック
37 ls $stsdir/*.err >& /dev/null
38 if [ $? -eq 0 ];then return 99;fi
39
40 # errファイルが無い場合は、endファイルの存在チェックを行う
41 completeFlg=1
42 while IFS=, read -a line;do
43 job="${line[0]}"
44 # endファイルが無い場合は未完了
45 if [ ! -f $stsdir/P_$job.end ];then
46 completeFlg=0
47 break
48 fi
49 done < $pwd/jobflow.csv.tmp
50 return $completeFlg
51}
52
53# メイン処理
54cd $p_jobdir
55while :;do
56 # 全ジョブ完了チェック。完了していたら終了する。
57 completeCheck
58 ret=$?
59 if [ $ret -eq 1 ];then
60 echo "全てのジョブが完了しました"
61 exit 0
62 elif [ $ret -eq 99 ];then
63 echo "異常終了したジョブが見つかったためジョブフローを終了します。"
64 exit 1
65 fi
66
67 # ジョブの実行。csvを走査し、依存ジョブが全て正常終了していることを確認後、ジョブを実行する。
68 while IFS=, read -a line;do
69 job="${line[0]}" # 今から実行しようとしているジョブ名を取得
70 if [ -f $stsdir/P_$job.start ];then continue; fi # startファイルがあればすでに実行中なのでスキップ
71 if [[ ${line[@]:1} =~ ^( )*$ ]];then # 依存ジョブ(2列目以降)が無ければ、即時実行
72 echo $job を実行します
73 ./P_$job &
74 continue
75 fi
76 execflg="OK"
77 for prev in "${line[@]:1}";do # 依存ジョブ(2列目以降)を繰り返し取得
78 if [ "$prev" == "" ];then continue;fi # 取得した依存ジョブが空ならスキップ
79 if [ ! -f $stsdir/P_${prev}.end ];then # 依存ジョブが未実行・実行中・エラー終了の場合は実行不可
80 execflg="NG"
81 break
82 fi
83 done
84 if [ "$execflg" == "OK" ];then # 実行可能と判断された場合は実行
85 echo $job を実行します
86 ./P_$job &
87 fi
88 done < $pwd/jobflow.csv.tmp
89 sleep 1
90done
91cd $pwd
92
93exit 0
94
実行例
適当なディレクトリに J01 から J09 までのジョブ(シェルスクリプト)を作成した。 その内容は sleep コマンドの一行だけとした。sleep に渡す秒数は以下とした。
- J01 : sleep 10
- J02 : sleep 2
- J03 : sleep 3
- J04 : sleep 4
- J05 : sleep 5
- J06 : sleep 6
- J07 : sleep 7
- J08 : sleep 8
- J09 : sleep 9
実行結果
1$ ./jobctl ../sh
2J01 を実行します
3J02 を実行します
4J03 を実行します
5J04 を実行します
6J07 を実行します
7J06 を実行します
8J05 を実行します
9J09 を実行します
10J08 を実行します
11全てのジョブが完了しました
12$
13$
14$ ls -1 log
15P_J01.log
16P_J02.log
17P_J03.log
18P_J04.log
19P_J05.log
20P_J06.log
21P_J07.log
22P_J08.log
23P_J09.log
24$
25$
26$ cat log/*
27J01 開始 : 2024年 3月 3日 日曜日 11時05分46秒 JST
28J01 正常終了 : 2024年 3月 3日 日曜日 11時05分56秒 JST
29J02 開始 : 2024年 3月 3日 日曜日 11時05分47秒 JST
30J02 正常終了 : 2024年 3月 3日 日曜日 11時05分49秒 JST
31J03 開始 : 2024年 3月 3日 日曜日 11時05分46秒 JST
32J03 正常終了 : 2024年 3月 3日 日曜日 11時05分50秒 JST
33J04 開始 : 2024年 3月 3日 日曜日 11時05分47秒 JST
34J04 正常終了 : 2024年 3月 3日 日曜日 11時05分51秒 JST
35J05 開始 : 2024年 3月 3日 日曜日 11時05分57秒 JST
36J05 正常終了 : 2024年 3月 3日 日曜日 11時06分02秒 JST
37J06 開始 : 2024年 3月 3日 日曜日 11時05分51秒 JST
38J06 正常終了 : 2024年 3月 3日 日曜日 11時05分57秒 JST
39J07 開始 : 2024年 3月 3日 日曜日 11時05分46秒 JST
40J07 正常終了 : 2024年 3月 3日 日曜日 11時05分53秒 JST
41J08 開始 : 2024年 3月 3日 日曜日 11時06分03秒 JST
42J08 正常終了 : 2024年 3月 3日 日曜日 11時06分11秒 JST
43J09 開始 : 2024年 3月 3日 日曜日 11時05分58秒 JST
44J09 正常終了 : 2024年 3月 3日 日曜日 11時06分07秒 JST
45$
図示すると実行順序が分かりやすい。 少しのタイムラグがあるものの待機時間はほとんどなく、 jobflow.csv に従って実行されていることがわかる。