メンテナンス期間の設定のお話 【MIRACLE ZBX 全般】
ちょっと解りにくいメンテナンスの起点のはなし
当社のサポート宛に、「メンテナンスを隔週で設定したのに、メンテナンスモードにならなかった。」という質問が来ました。
えっ、え~と… 私自身はソースコードを以前に読んだので理解していたし、結構知られていることかな~と思って、どこかに書くこともしていなかったのですが、どうやらどこにもドキュメントが存在しません(汗)。
ということで、いつものようにソースコードを追いつつ解説です。
メンテナンス期間適合のアルゴリズム
今回は、解説する項目が二点あるので、最初におおまかなアルゴリズムの解説です。
(個人的には、「メインテナンス」と記述するのが好きなので、ちょっと記事が書き辛い…)
メンテナンス期間に関する処理は、Timerプロセスにて行われます。ソースコードは、src/zabbix_server/timer/timer.c となります。とりあえず、2.0.16のものを見ていきますが、どのバージョンも大差ありません。
681 static void process_maintenance(void)
682 {
...
DBからメンテナンス期間の抽出
...
個々のメンテナンスについての処理
717 while (NULL != (row = DBfetch(result)))
718 {
...
「期間のタイプ」別に直近のメンテナンス開始日時を計算
731 switch (db_timeperiod_type) {
...
815 default:
816 continue;
817 }
...
現在時刻がメンテナンス期間に入っているかの判定
819 /* allow one time periods to start before active time */
820 if (db_start_date < db_active_since && TIMEPERIOD_TYPE_ONETIME != db_timeperiod_type)
821 continue;
822
823 if (db_start_date > now || now >= db_start_date + db_period)
824 continue;
825
826 maintenance_from = db_start_date;
827
828 if (maintenance_from < db_active_since)
829 maintenance_from = db_active_since;
...
ホストに対してメンテナンスフラグを立てる
831 process_maintenance_hosts(&hm, &hm_alloc, &hm_count, maintenance_from, db_maintenanceid, db_maintenance_type);
832 }
833 DBfree_result(result);
834
実際にDBへ反映
835 update_maintenance_hosts(hm, hm_count, (int)now);
大まかな流れとしては、DBからメンテナンスのリストを取得した後に、それぞれのメンテナンス情報に対して直近の開始日を計算し、現在時刻がメンテナンス期間の範囲に入っているかどうかを判定するというアルゴリズムになっています。
そして、「期間のタイプ」ごとの処理の中で、起点がどこであるか?といったことが記述されています。
期間のタイプを「毎週」に設定した場合
前節の「『期間のタイプ』ごとの処理」を「毎週」に設定した場合の処理について解説します。
681 static void process_maintenance(void)
682 {
...
748 case TIMEPERIOD_TYPE_WEEKLY:
749 db_start_date = now - sec + db_start_time;
750 if (sec < db_start_time)
751 db_start_date -= SEC_PER_DAY;
752
753 if (db_start_date < db_active_since)
754 continue;
755
756 tm = localtime(&db_active_since);
757 wday = (0 == tm->tm_wday ? 7 : tm->tm_wday) - 1;
758 active_since = db_active_since - (wday * SEC_PER_DAY + tm->tm_hour * SEC_PER_HOUR + tm->tm_min * SEC_PER_MIN + tm->tm_sec);
759
760 for (; db_start_date >= db_active_since; db_start_date -= SEC_PER_DAY)
761 {
762 /* check for every x week(s) */
763 week = (db_start_date - active_since) / SEC_PER_WEEK + 1;
764 if (0 != (week % db_every))
765 continue;
766
767 /* check for day of the week */
768 tm = localtime(&db_start_date);
769 wday = (0 == tm->tm_wday ? 7 : tm->tm_wday) - 1;
770 if (0 == (db_dayofweek & (1 << wday)))
771 continue;
772
773 break;
774 }
775 break;
db_start_timeはWeb UI上での「開始時刻 (時:分)」、db_active_sinceは「開始日時」となります。
secは、本日の0時0分0秒からの経過秒となります。
l.749で、(now - sec)は本日の0時0分0秒となり、db_start_dateは、「本日の開始時刻 (時:分)」となります。ただし、この値はあくまで仮想的なものです。
次に、ll.756-758の処理で起点の計算をしています。
l.757で、wdayは{日、月、火、水、木、金、土}={6, 0, 1, 2, 3, 4, 5}となります。
そして、l.758により、起点であるactive_sinceは「開始日時」の週の月曜日の0時0分0秒となることがわかります。ぱっと見ではわかりづらいですが、db_active_sinceが月曜日のある時刻であると想像して、計算してみましょう。同じように、火曜日、水曜日の場合もたどっていけばよいでしょう。
ll.763-765の処理では、計算したactive_sinceを起点とし、db_start_dateが第何週目かを計算し、設定した間隔にマッチしているかを調べています。
これより、「繰返し間隔(週)」を「3」と設定した場合は、xxoxxo...のように(3n)周目がマッチすることがわかります。(最初の週が第一週で、(3n)のときに、l.764の判定を抜けて下の処理に入ります。)
また、ll.768-771では、該当する曜日であるかの判定を行っています。
上に戻りますが、途中 continue; で繰り返しとなった場合、l.760でfor()文に戻り、db_start_date -= SEC_PER_DAY として一週間遡って直近のメンテナンス日を計算します。
その後に、計算したdb_start_dateを用いて現在時刻が範囲に収まっているかを判定して、直近のメンテナンス期間を決定します。
結論としては、
「期間のタイプ」を「毎週」にしたときの起点は月曜日の0時0分0秒となります。また、第一週目の判定は「開始日時」が含まれる週となります。「繰返し間隔(週)」を「3」と設定した場合、第(3n)週目が該当週となります。
ということになります。
ついでなんで、期間のタイプを「毎日」に設定した場合
681 static void process_maintenance(void)
682 {
...
734 case TIMEPERIOD_TYPE_DAILY:
735 db_start_date = now - sec + db_start_time;
736 if (sec < db_start_time)
737 db_start_date -= SEC_PER_DAY;
738
739 if (db_start_date < db_active_since)
740 continue;
741
742 tm = localtime(&db_active_since);
743 active_since = db_active_since - (tm->tm_hour * SEC_PER_HOUR + tm->tm_min * SEC_PER_MIN + tm->tm_sec);
744
745 day = (db_start_date - active_since) / SEC_PER_DAY + 1;
746 db_start_date -= SEC_PER_DAY * (day % db_every);
747 break;
ll.734は、l.749と同じです。
l.749で、(now - sec)は本日の0時0分0秒となり、db_start_dateは、「本日の開始時刻 (時:分)」となります。
l.743のactive_sinceは、「開始日時」の日の0時0分0秒となります。
l.745で、db_start_dateが起点となる日から第何日目であるかを計算し(day)、l.746で「仮の本日の開始時刻 (時:分)」から「dayと周期の剰余分の秒数」を引くことで、直近の開始時刻を計算しています。また、この計算には「毎週」の場合と異なり、ループで遡って計算する必要はありません。
(こういう難しい計算は、やっぱり適当な日時を当てはめて確認することがよいと思います。)
結論としては、期間のタイプが「毎週」である場合と同じように、
「期間のタイプ」を「毎日」にしたときの起点は「開始日時の」0時0分0秒となります。「繰返し間隔(日)」を「3」と設定した場合、第(3n)日が該当日となります。
となります。
ただし、第何日目かを自分で理解するのは難しいですよね。
メンテナンス期間がずれたときは?
メンテナンス間隔がずれた場合などは、狙った日がターゲットになるように遡って設定する必要があります。
例えば、来週行う予定であったメンテナンスを、今週の木曜日にし、以降3週おきに行うといった場合、「開始日時」を二週間前(とうぜん、起点は月曜日の00:00:00ですよ)にして、設定をすることになります。
過去に遡って設定しても、判定には設定した時刻を用いていないため、問題ありません。