採用情報

お問い合わせ

BLOG

Zabbix テック・ラウンジ

2019 年 01 月 08 日

Zabbix 3.2以降の新機能解説(Zabbix 4.0を見据えて) その19 - トリガーベースのメインテナンス(その2)

こんにちは、MIRACLE ZBX サポートを担当している花島タケシです。今回は、Timer プロセスによる後処理について解説します。

トリガーベースのメインテナンス ( その 2)

通常メンテナンスと「抑止」の後処理

前回解説したトリガーベースのメインテナンスですが、「抑止機能」と呼んだ方が良いのでしょうか ? 今回はこちらの Timer プロセスによる後処理について解説します。

Zabbix 4.0 では、Timer プロセスの処理は以下二点を行うように変更されています。
・通常のメインテナンス処理。タグ等に関係なく設定されたメインテナンス
に則り、ホストをメインテナンス状態に移行、及びその逆を行う。
・抑止機能にて保存されたデータの更新、削除、並びに新規登録を行う。

通常の時刻ベースでのメインテナンス処理に関しては、以前のバージョンと大差はありません。
ただし、コード自体は以前よりクリーンアップされています。

実際のコードを見ていこう !

Timer プロセスの処理がマルチプロセス化されたことも含めて再度コードを見ていきます。
最初にメインループとなります。

src/zabbix_server/timer/timer.c

696 for (;;)
697 {
698 sec = zbx_time();
699
700 if (1 == process_num)
701 {
702 /* start update process only when all timers have finished their updates */
703 if (sec - maintenance_time >= ZBX_TIMER_DELAY && FAIL == zbx_dc_maintenance_check_update_flags())
704 {
...
722 if (SUCCEED == update)
723 {
724 zbx_dc_maintenance_set_update_flags();
725 db_update_event_suppress_data(&events_num);
726 zbx_dc_maintenance_reset_update_flag(process_num);
727 }
728 else
729 events_num = 0;
...
736 update_time = (int)sec;
737 }
738 }
739 else if (SUCCEED == zbx_dc_maintenance_check_update_flag(process_num))
740 {
...
750 update_time = (int)sec;
751 zbx_dc_maintenance_reset_update_flag(process_num);
752 }
...
754 if (maintenance_time != update_time)
755 {
756 maintenance_time = (int)sec;
757
758 if (0 > (idle = ZBX_TIMER_DELAY - (zbx_time() - sec)))
759 idle = 0;
760
761 zbx_setproctitle("%s #%d [%s, idle %d sec]",
762 get_process_type_string(process_type), process_num, info, idle);
763 }
764
765 if (0 != idle)
766 zbx_sleep_loop(1);
767
768 idle = 1;
...
775 }

l.696 からが Timer プロセスのメインループ処理となります。l.700 と l.739 において Timer プロセスの ( 内部的な ) プロセス番号により処理が異なります。プロセス番号の一番小さい 1 番のプロセス (#1 と呼びます ) は、l.700 からのブロックでの処理となり、その他は l.739 での処理となります。

ところで、#1 以外はなぜ、l.739 にて else {... という単純な処理にならず、zbx_dc_maintenance_check_update_flag() による判定となっているのでしょう ? それは、#1 がゲートオープナーの役割を担い、#1 が許可をした場合のみ #1 以外が処理を進める仕組みとなっているからです。

#1 によるゲートオープン

src/libs/zbxdbcache/dbconfig_maintenance.c

712 int zbx_dc_maintenance_check_update_flags(void)
713 {
714 int slots_num = ZBX_MAINTENANCE_UPDATE_FLAGS_NUM(), ret = SUCCEED;
715
716 RDLOCK_CACHE;
717
718 if (0 != config->maintenance_update_flags[0])
719 goto out;
720
721 if (1 != slots_num)
722 {
723 if (0 != memcmp(config->maintenance_update_flags, config->maintenance_update_flags + 1, slots_num - 1))
724 goto out;
725 }
726
727 ret = FAIL;
728 out:
729 UNLOCK_CACHE;
730
731 return ret;
732 }

Timer のどのプロセスが現在稼働中であるかは ? は、特定の変数の特定のビットの上げ下げで管理しています。その変数は 64 ビットの配列となります。具体的には上記コードの、l.718 で使用される config->maintenance_update_flags となります。

zbx_dc_maintenance_check_update_flags() は、どれか一つでも稼働していると SUCCEED が返るように実装されています。最初に l.718 にて配列の最初の要素だけをチェックしています。
ll.721-725 のブロックは 65 以上の Timer プロセスを起動した場合の判定となります。l.723 の memcmp() にて全ての要素が 0 であるかの検査をしています。しかし、この二段階の判定がコードは複雑にしていますが、高速化にどれだけ寄与しているのか疑問です。( 要は、最初から全ての要素に対して memcmp() すれば良いのでは ? ということです )

結果として、どれか一つでも処理が継続されていたなら SUCCEED が返り、l.703 の判定は偽となるため、#1 はその後の処理を行わなくなり、ゲートも開きません。つまり、Timer プロセスは 30 秒に一回の処理となりますが、前回の処理が終わっていないプロセスがあると、当回はスキップするということです。

無事、前回処理が終了されていることを確認できたら、#1 は l.723 にて
zbx_dc_maintenance_set_update_flags() にてゲートオープンをします。
かなり端折っていますが、直前の判定で用いられている update 変数は、メインテナンス期間に入っているメインテナンスがあるかどうか ? ということです。

629 void zbx_dc_maintenance_set_update_flags(void)
630 {
631 int slots_num = ZBX_MAINTENANCE_UPDATE_FLAGS_NUM(), timers_left;
632
633 WRLOCK_CACHE;
634
635 memset(config->maintenance_update_flags, 0xff, sizeof(zbx_uint64_t) * slots_num);
636
637 if (0 != (timers_left = (CONFIG_TIMER_FORKS % (sizeof(uint64_t) * 8))))
638 config->maintenance_update_flags[slots_num - 1] >>= (sizeof(zbx_uint64_t) * 8 - timers_left);
639
640 UNLOCK_CACHE;
641 }

それほど複雑な処理ではありません。l.635 にて全てのビットを上げています。
ll.637-638 では、未使用の部分 ( たとえば、Timer プロセスを 30 上げた場合、管理領域は 64 ごとであるため 34 余ります。) を下げています。
これでゲートが開かれたこととなります。

ゲートのクローズ

クローズというよりも、各プロセスがフラグを下げる処理となります。
#1 に限らず、その他のプロセスも zbx_dc_maitenance_reset() をコールしています。
l.637 と l.751 になります。

652 void zbx_dc_maintenance_reset_update_flag(int timer)
653 {
654 int slot, bit;
655 zbx_uint64_t mask;
656
657 timer--;
658 slot = timer / (sizeof(uint64_t) * 8);
659 bit = timer % (sizeof(uint64_t) * 8);
660
661 mask = ~(__UINT64_C(1) << bit);
662
663 WRLOCK_CACHE;
664
665 config->maintenance_update_flags[slot] &= mask;
666
667 UNLOCK_CACHE;
668 }

説明するほどでもありませんね。前半で該当するビット位置を計算して、mask( 該当する位置が 0 で他が 1) を生成します。
l.665 にて論理積を取ることにより、特定のビットを下げることができます。

下図が大まかなフローとなります。

抑止機能後処理

なが~い、長いゲートの話が終わって、ようやく抑止機能後処理の解説に入ります。( 正直ビット処理を長々と書く必要もないとは思ってはいますが。)

db_update_event_suppress_data() がその処理となります。l.725 と l.744 にて呼び出されています。timer.c に記述されていますが、とにかく長い関数です。

462 static void db_update_event_suppress_data(int *suppressed_num)
463 {
...
471 db_get_query_events(&event_queries, &event_data);
472
473 if (0 != event_queries.values_num)
474 {
...
495 if (0 != maintenanceids.values_num && SUCCEED == zbx_db_lock_maintenanceids(&maintenanceids))
496 zbx_dc_get_event_maintenances(&event_queries, &maintenanceids);
497
498 zbx_db_insert_prepare(&db_insert, "event_suppress", "event_suppressid", "eventid", "maintenanceid",
499 "suppress_until", NULL);
501
500 DBbegin_multiple_update(&sql, &sql_alloc, &sql_offset);
502 for (i = 0; i < event_queries.values_num; i++)
503 {
504 query = (zbx_event_suppress_query_t *)event_queries.values[i];
505 zbx_vector_uint64_pair_sort(&query->maintenances, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
506
507 k = 0;
508
509 if (FAIL != (j = zbx_vector_ptr_bsearch(&event_data, &query->eventid,
510 ZBX_DEFAULT_UINT64_PTR_COMPARE_FUNC)))
511 {
...
517 while (j < data->maintenances.values_num && k < query->maintenances.values_num)
518 {
519 if (data->maintenances.values[j].first < query->maintenances.values[k].first)
520 {
/* 削除対象の抽出となります */
527 }
528
529 if (data->maintenances.values[j].first > query->maintenances.values[k].first)
530 {
/* 新規登録対象の抽出となります */
543 }
544
545 if (data->maintenances.values[j].second != query->maintenances.values[k].second)
546 {
/* 更新対象の抽出となります */
558 }
...
568 }
569 }
570
...
622 }

最初に、l.471 の db_get_query_events() にて、event_queries と event_data を生成します。
event_queries は problem テーブルから生成した情報となり、トリガー由来のものだけとなります。つまり、現在障害となっているイベント情報とほぼ同じです。
event_data は、event_suppress テーブルから生成されます。つまり、抑止されたデータとなります。(DB Syncer がトリガー判定後に生成したものであるため )
event_queries にはさらに、event_suppress テーブルには存在するが、problem テーブルには存在しないデータも付与されます。
なお、Timer プロセスが複数起動しますが、全体数と自身のプロセス番号での剰余を用いて、処理する数の分散化を行っています。

ll.502-582 にて、event_queries の個々のデータに対して、長々と処理をします。
具体的には、((problem テーブルにある ) 障害となったデータの )eventid から、該当するデータが event_data に存在するかの判別を、l.509 にて行います。
その後の処理はメインテナンスについては下記のように行います。
・event_queries には存在するが、event_data には存在しないものは、既にメインテナンス
期間が終わったと判断し、削除対象とする。(ll.519-527)
・event_queris には存在しないが、event_data には存在するものは、既に障害となった後に
メインテナンス期間になったため、event_suppress テーブルへデータを挿入する。(ll.529-543)
・どちらにも存在するものは、更新対象とする。(ll.545-558)

最後に

少しまとまりのないものとなってしまいましたが、Timer プロセスの処理について解説を行いました。
4.0 においては、それまでの Timer プロセスの処理と異なり、トリガー評価を行うことなく、メインテナンスのみの処理を行うようになっています。

関連記事

Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 1
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 2
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 3
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 4
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 5
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 6
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 7
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 8
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 9
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 10
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 11
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 12
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 13
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 14
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 15
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 16
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 17
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 18
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 19
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 20
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 21

注意事項

  • 本ドキュメントの内容は、予告なしに変更される場合があります。
  • 本ドキュメントは、限られた評価環境における検証結果をもとに作成しており、全ての環境での動作を保証するものではありません。
  • 本ドキュメントの内容に基づき、導入、設定、運用を行なったことにより損害が生じた場合でも、当社はその損害についての責任を負いません。あくまでお客さまのご判断にてご使用ください。
CentOS 7 延長サポートサービス
デジタルトランスフォーメーションのための電子認証基盤 iTrust
SSL/TLS サーバー証明書 SureServer Prime