内部チェック zabbix[process,,,] は何を取得しているのか?
こんにちは。ミラクル・リナックスの「KO太郎」です。
zabbix[process,...」では Zabbix Server で動作するプロセスのビジー/アイドル率を取得取得することができます。
しかし、実際にはどのような値を取得しているのかマニュアルからはわかりづらいところです。どのようにしてアイテムのデータを取得しているか、ソースコードを調査して確認します。
調査環境
MIRACLE ZBX 2.2.11
概要
アイテムの値を取得するまでのおおまかな処理の流れは下記となります。
1. 各プロセスのステイタスがbusyであった時間とidleであった時間を記録する
2. self-monitoringプロセスが各プロセスの記録を集計する
3. アイテムの値を取得する(集計されたデータを使用する)
1.各プロセスのビジー/アイドル時間を記録する
Zabbix Serverで動作するプロセスは、起動してから主要な処理を行いスリープするということを繰り返しています。プロセスはスリープする時にそれまでの時間をbusyの時間に記録し、スリープが終わった時にそれまでの時間をidleとして記録します。各プロセスがビジー/アイドルである時間を記録する処理は各プロセス自身が行います。
ソースコードを確認します。
src/zabbix_server/poller/poller.c
752 void main_poller_loop(unsigned char poller_type)
753 {
...
766 for (;;)
767 {
...
775 sec = zbx_time();
776 processed += get_values(poller_type);
777 total_sec += zbx_time() - sec;
778
779 nextcheck = DCconfig_get_poller_nextcheck(poller_type);
780 sleeptime = calculate_sleeptime(nextcheck, POLLER_DELAY);
...
802 zbx_sleep_loop(sleeptime);
803 }
pollerプロセスを例として挙げています。
プロセスは起動してからアイテムを取得して、zbx_sleep_loop()をコールすることを繰り返します。その他のプロセス(trapper以外の)も大体同じ作りになっています。プロセスがアイドル/ビジー状態でいた時間の記録はzbx_sleep_loop()で行われます。
src/libs/zbxself/selfmon.c
512 void zbx_sleep_loop(int sleeptime)
513 {
...
519 update_selfmon_counter(ZBX_PROCESS_STATE_IDLE);
...
530 sleep(sleeptime);
531
532 update_selfmon_counter(ZBX_PROCESS_STATE_BUSY);
533 }
zbx_sleep_loop()では、
1.アイドルのステイタス(ZBX_PROCESS_STATE_IDLE)をupdate_selfmon_counter()に渡す
2.sleep()する
3.ビジーのステイタス(ZBX_PROCESS_STATE_BUSY)をupdate_selfmon_counter()に渡す
という処理が行なわれます。
update_selfmon_counter()の処理を確認します。
src/libs/zbxself/selfmon.c
333 void update_selfmon_counter(unsigned char state)
334 {
...
342 process = &collector->process[process_type][process_num - 1];
343 ticks = times(&buf);
344
345 LOCK_SM;
346
347 if (ticks > process->last_ticks)
348 process->counter[process->last_state] += ticks - process->last_ticks;
349 process->last_ticks = ticks;
350 process->last_state = state;
351
352 UNLOCK_SM;
353 }
時間を保存する領域(collector)は共有メモリ上に確保されています。そのため、操作を行う際はミューテックスの獲得と解除がされます(LOCK_SMとUNLOCK_SM)。また、その領域の確保と初期化はsrc/libs/zbxself/selfmon.c のinit_selfmon_collector()で行われています。
時間を保存する領域(collector)には、アイドルの時間を記録するカウンタprocess->counter[ZBX_PROCESS_STATE_IDLE])と
ビジーの時間を記録するカウンタ(process->counter[ZBX_PROCESS_STATE_BUSY]の2つのカウンタがあり、
それぞれsleepしていた時間とそれ以外の時間が記録されます。
update_selfmon_counter()では下記の処理が行われています。
1.現在のクロック数と前回のクロック数(last_clock)の差分を、前回引数に渡したステイタス(last_state)の時間のカウンタ(counter[last_state])に加算する
2.現在のクロック数をlast_clockにセットする
3.引数のステイタスをlast_stateにセットする
このようにして、プロセスがsleep()を実行している時間がアイドルの時間として記録され、それ以外の時間がビジーとして記録されています。
2. self-monitoringプロセスが各プロセスの記録を集計する
次に、記録した時間の集計を行うself-monitoringプロセスの処理を見ていきます。
self-monitoringプロセスは、平均値や最大最小値をもとめるために、上記の処理で収集した時間を集計しています。
src/zabbix_server/selfmon/selfmon.c
28 void main_selfmon_loop(void)
29 {
30 double sec;
31
32 for (;;)
33 {
...
37 collect_selfmon_stats();
...
43 zbx_sleep_loop(1);
44 }
45 }
self-monitoringプロセスは、collect_selfmon_stats()をコールして1秒スリープすることを繰り返しています。
collect_selfmon_stats()の処理を確認します。
src/libs/zbxself/selfmon.c
362 void collect_selfmon_stats(void)
363 {
...
373 ticks = times(&buf);
...
377 if (MAX_HISTORY <= (index = collector->first + collector->count))
378 index -= MAX_HISTORY;
379
380 if (collector->count < MAX_HISTORY)
381 collector->count++;
382 else if (++collector->first == MAX_HISTORY)
383 collector->first = 0;
...
385 for (proc_type = 0; proc_type < ZBX_PROCESS_TYPE_COUNT; proc_type++)
386 {
387 process_forks = get_process_type_forks(proc_type);
388 for (proc_num = 0; proc_num < process_forks; proc_num++)
389 {
390 process = &collector->process[proc_type][proc_num];
391 for (state = 0; state < ZBX_PROCESS_STATE_COUNT; state++)
392 process->h_counter[state][index] = process->counter[state];
393 if (ticks > process->last_ticks)
394 process->h_counter[process->last_state][index] += ticks - process->last_ticks;
395 }
この関数では各プロセスにおいて収集されているデータ(counter)を集計したデータを保存する領域(h_counter)にコピーしています。h_counterには、60個(MAX_HISTORY)のコピーする領域があります。また、2つのカウンタ(countとfirst)を使用して、この領域に60個のデータを確保しています。プロセスはこの関数を実行して1秒スリープすることを繰り返しているので、約60秒分のデータをh_counterに確保することになります。
393行目では、プロセスで取得したクロック数(last_ticks)が現時点のクロック数よりも小さい場合、その差分を加算しています。これによりプロセスがデータを収集してから、この処理で集計するまでの時間を加算しています。
3. アイテムの値を取得する(集計されたデータを使用する)
データの取得はpollerプロセスで行われます。
主な処理はget_selfmon_stats()で行われ、下記の流れでコールされています。
main_poller_loop()
→get_values()
→get_value()
→get_value_internal()
→get_selfmon_stats()
get_value_internal()の処理から見ていきます。
コードの途中に解説をコメントします。
src/zabbix_server/poller/checks_internal.c
47 int get_value_internal(DC_ITEM *item, AGENT_RESULT *result)
48 {
...
240 else if (0 == strcmp(tmp, "process")) /* zabbix["process",<type>,<mode>,<state>] */
241 {
...
286 process_forks = get_process_type_forks(process_type);
287
...
291 if (0 == strcmp(tmp, "count"))
292 {
...
298
299 SET_UI64_RESULT(result, process_forks);
300 }
/*
get_process_type_forks(process_type)では、<type>に指定したプロセスが動作している数を取得します。
<mode> に count が指定した場合は、この数が結果として返ります。
*/
...
326 else if (process_num > process_forks)
327 {
328 error = zbx_dsprintf(error, "\"%s\" #%d is not started",
329 get_process_type_string(process_type), process_num);
330 goto notsupported;
331 }
...
/*
<mode> に存在しないプロセスの番号(※)を指定した場合、 NOTSUPPORTEDを返します。
*/
..
346 get_selfmon_stats(process_type, aggr_func, process_num, state, &value);
347
348 SET_DBL_RESULT(result, value);
349 }
...
568 notsupported:
569 if (!ISSET_MSG(result))
570 {
571 if (NULL == error)
572 error = zbx_strdup(error, "Internal check is not supported");
573
574 SET_MSG_RESULT(result, error);
575 }
576
577 return NOTSUPPORTED;
578 }
※<mode>には対象とするプロセスの番号を指定することができます。
番号はzabbix server の起動時のログに出力されます。
"[プロセス名 #]"の後の数字です。
例)Zabbix Server 起動時のログ
28159:20160111:100742.890 server #20 started [history syncer #1]
28160:20160111:100742.890 server #21 started [history syncer #2]
28161:20160111:100742.891 server #22 started [history syncer #3]
つづいて、get_selfmon_stats()を確認します。
src/libs/zbxself/selfmon.c
420 void get_selfmon_stats(unsigned char proc_type, unsigned char aggr_func, int proc_num,
421 unsigned char state, double *value)
422 {
...
455 for (; proc_num < process_forks; proc_num++)
456 {
457 zbx_stat_process_t *process;
458 unsigned short one_total = 0, one_counter;
459
460 process = &collector->process[proc_type][proc_num];
461
462 for (s = 0; s < ZBX_PROCESS_STATE_COUNT; s++)
463 one_total += process->h_counter[s][current] - process->h_counter[s][collector->first];
464
465 one_counter = process->h_counter[state][current] - process->h_counter[state][collector->first];
/*
one_totalにはそのプロセスのステイタスがbusyであった時間とidleであった時間の合計がはいります。
one_counterにはプロセスがstateで指定したステイタスでいた時間が入ります。
busy/idleの時間は、最新の値(h_counterのcurrent)から約60秒前の値(first)を引いて算出します。
*/
...
467 switch (aggr_func)
468 {
469 case ZBX_AGGR_FUNC_ONE:
470 case ZBX_AGGR_FUNC_AVG:
471 total += one_total;
472 counter += one_counter;
473 break;
/*
<mode>にavg(ZBX_AGGR_FUNC_AVG)かプロセスの番号(ZBX_AGGR_FUNC_ONE)を指定した場合
avgの場合、すべてのプロセスの時間を合計した値で算出されます。
プロセス番号を指定した場合、指定した1プロセスのみについて算出されます。
両方とも、494行目の計算式で算出されます。
*/
474 case ZBX_AGGR_FUNC_MAX:
475 if (0 == proc_num || one_counter > counter)
476 {
477 counter = one_counter;
478 total = one_total;
479 }
480 break;
481 case ZBX_AGGR_FUNC_MIN:
482 if (0 == proc_num || one_counter < counter)
483 {
484 counter = one_counter;
485 total = one_total;
486 }
487 break;
/*
<mode>にmax/minを指定した場合
プロセスのなかで<state>に指定した時間が最も大きい/小さいプロセスを選択して、その1プロセスのみのについて算出します。
同じく494行目の計算式で算出されます。
*/
488 }
489 }
490
491 unlock:
492 UNLOCK_SM;
493
494 *value = (0 == total ? 0 : 100. * (double)counter / (double)total);
...
497 }
まとめ
zabbix[process,<type>,<mode>,<state>]の実装を調査しました。
zabbix[process,...]のbusy率が100%になることがあります。このことは、アイテム取得時から過去約60秒間すべてがsleep()していない時間であるということを示しています。sleep()していない時間何をしていたのかはこの情報からは特定することができません。プロセスが共有メモリのロック獲得を待っていたり、DBへの書き込み処理に時間がかかっているといったさまざまなことが考えられます。
マニュアルには「将来、ロック待ちやデータベースクエリーの実行といった状態が追加導入されるかもしれません。」という記述があります。これについてまだ正式には公開されていないMIRACLE ZBX 3.0.x について確認しましたが、いまのところ実装されていません。
「統合システム監視ソリューション MIRACLE ZBX」の製品やサポートサービスについてはこちらをご覧ください。