ジョブネットとコントローラの自作

ジョブネットとコントローラを自作した。備忘録として載せておく。
インプットは下図。前提としてjob列の実行ファイルは作成済みであること。 prev は先行ジョブで、全ての先行ジョブが完了してからカレントジョブを実行する。 例えば、J04 の実行は J02, J03 の完了後に開始する。
status の値は 0(正常終了)、1(異常終了)、2(実行中)、9(未実行)を示す。
stime, etime はそれぞれ開始時刻、終了時刻を示す。

このインプットをCSVで保存する(jobnet.csv)。

◆ jobnet.csv

 1job,status,stime,etime,prev1,prev2
 2J01,9,,,Start,
 3J02,9,,,J01,
 4J03,9,,,J01,
 5J04,9,,,J02,J03
 6J05,9,,,J04,
 7J06,9,,,J04,
 8J07,9,,,J05,J06
 9J08,9,,,J05,J06
10J09,9,,,J07,
11J10,9,,,J08,
12J11,9,,,J10,
13J12,9,,,J09,J11

◆jobnet.csv の更新の様子
下図は jobnet.csv 更新概念図。J01という親 job の中に複数の子 job がいる。 親 job の先頭と末尾で jobnet.csv を更新、エラー時も更新している。 親 job の先頭では jobnet.csv の status を2(実行中)に更新、開始日時 stime も現在日時で更新する。 親 job の末尾では status を0(正常終了)に更新、終了日時 etime も現在日時で更新する。

以下で示すプログラムを実行した時の、jobnet.csv が更新されていく様子。

◆各ジョブのサンプルコード(J01)
メイン処理の前後で jobnet.csv の stime, etime を更新している。ここではテストのため、子jobをただ一つの sleep だけ、さらにエラー判定も省略している。

 1#!/bin/bash
 2
 3job=`echo $0 | sed -E 's/.+\/([^\/]+)$/\1/'`
 4stime=`date +"%Y/%m/%d %H:%M:%S"`
 5awk -v job=$job -v stime="$stime" 'BEGIN{FS=OFS=","}$1==job{$2=2;$3=stime}{print}' jobnet.csv 1<> jobnet.csv
 6
 7#メイン処理実行(スリープでテスト)
 8sleep 3 
 9sts=$?
10
11etime=`date +"%Y/%m/%d %H:%M:%S"`
12awk -v job=$job -v etime="$etime" -v sts=$sts 'BEGIN{FS=OFS=","}$1==job{$2=sts;$4=etime}{print}' jobnet.csv 1<> jobnet.csv
13
14exit 0;

◆ bashプログラム(jobnet.sh)
二つの while ループがある。 一つ目は選択画面の表示と入力チェック、二つ目は jobnet.csv の監視とjob実行を行うメイン処理である。 job はバックグラウンドで実行する(並列稼働を可能とするため)

 1#!/bin/bash
 2
 3if [ ! -r jobnet.csv ];then
 4    echo "jobnet.csv がありません"
 5    exit 1
 6fi
 7if [ ! -r jobnet.awk ];then
 8    echo "jobnet.awk がありません"
 9    exit 1
10fi
11
12while :
13do
14    echo "[1] ジョブネットの最初から実行する"
15    echo "[2] アベンドしたジョブ(1)や強制終了したジョブ(2)から再実行する"
16    echo "[9] 終了"
17    echo -n "番号を入力してEnterキーを押してください:"
18    read SELECT
19    
20    if [ $SELECT = "1" ]; then
21        # jobnet.csvの初期化
22        awk 'BEGIN{FS=OFS=","}NR != 1{$2=9;$3=$4=""}{print}' jobnet.csv > jobnet.tmp
23        mv jobnet.tmp jobnet.csv
24        echo "ジョブネットを開始します。強制終了する場合は Ctrl+C を長押ししてください"
25        awk -F, '$5=="Start"{system("./"$1" &")}' jobnet.csv
26        STS=$?
27        if [ $STS != "0" ]; then
28            exit 1
29        fi
30        break
31    elif [ $SELECT = "2" ]; then
32        echo "ジョブネットを再開します。強制終了する場合は Ctrl+C を長押ししてください"
33        awk -F, '$2~/(1|2)/{system("./"$1" &")}' jobnet.csv
34        STS=$?
35        if [ $STS != "0" ]; then
36            exit 1
37        fi
38        break
39    elif [ $SELECT = "9" ]; then
40        exit 0
41    else
42        echo -n "正しい番号を入力してEnterキーを押してください:"
43    fi
44done
45
46while :
47do
48    sleep 1
49    # prevのジョブが全て完了しているかのチェック
50    awk -F, -f jobnet.awk jobnet.csv
51    STS=$?
52    # いずれかのjobで異常終了が発生していた場合、全体を異常終了する
53    if [ $STS = "1" ]; then
54        echo 'jobnet でアベンド発生'
55        exit 1
56    elif [ $STS = "0" ]; then
57        break
58    fi
59done
60
61echo "全てのジョブが正常終了しました"
62exit 0;

◆awkプログラム(jobnet.awk)
jobnet.csv の prev に書かれている job(先行ジョブ)の status をチェックして、正常終了したらカレント job を実行する。 job はバックグラウンドで実行する(並列稼働を可能とするため)

 1NR == 1{next}       # 一行目はヘッダーなのでスキップ
 2$5 == "Start"{next} # Startのジョブはシェルで実行済みなのでスキップ
 3{
 4    # prev配列の初期化
 5    delete prev
 6	# statusが2(実行中)または9(未実行)の場合、終了ステータスを2(実行中)とする
 7	if($2 == 2 || $2 == 9) exitSts = 2
 8    # prevは4番目以降のフィールド
 9    for(i=5;i<=NF;i++){
10        # prevが未設定の場合はループを抜ける
11        if(!$i)break
12        # prevのjobのstatusを取得する
13        com = "awk -F, -v prev="$i" '$1==prev{print $2;exit}' jobnet.csv"
14        com | getline status
15        close(com)
16        # 取得したstatusをprev配列にセット
17        prev[$i] = status
18    }
19    # prev配列に1(異常終了)が含まれていた場合、直ちに全体を異常終了する
20    for(status in prev){
21        if(prev[status] == 1)exit(1)
22    }
23    # prev配列に2(実行中)または9(未実行)が含まれていた場合、カレントjobの処理をスキップ
24    for(status in prev){
25        if(prev[status] == 2 || prev[status] == 9) next
26    }
27    # prevが全て正常終了していて、かつ、カレントjobのstatusが9(未実行)の場合は、実行開始する
28    if($2 == 9)system("./"$1" &")
29}
30END{exit(exitSts)}

関連ページ