現在位置: ホーム / みらくるブログ / メンテナンス期間の設定のお話 【MIRACLE ZBX 全般】

メンテナンス期間の設定のお話 【MIRACLE ZBX 全般】

メンテナンス期間で、「期間のタイプ」を「毎週」にしたときの起点は月曜日の0時0分0秒となります。また、第一週目の判定は「開始日時」が含まれる週となります。「繰返し間隔(週)」を「3」と設定した場合、第(3n)週目が該当週となります。

ちょっと解りにくいメンテナンスの起点のはなし

当社のサポート宛に、「メンテナンスを隔週で設定したのに、メンテナンスモードにならなかった。」という質問が来ました。

えっ、え~と… 私自身はソースコードを以前に読んだので理解していたし、結構知られていることかな~と思って、どこかに書くこともしていなかったのですが、どうやらどこにもドキュメントが存在しません(汗)。

ということで、いつものようにソースコードを追いつつ解説です。

 

メンテナンス期間適合のアルゴリズム

今回は、解説する項目が二点あるので、最初におおまかなアルゴリズムの解説です。

(個人的には、「メインテナンス」と記述するのが好きなので、ちょっと記事が書き辛い…)

 

メンテナンス期間に関する処理は、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ですよ)にして、設定をすることになります。

過去に遡って設定しても、判定には設定した時刻を用いていないため、問題ありません。