0) { set_config_option('time_last_change_thold_log', time()); } } /** * Cacti poller output-processing hook: intercepts newly collected RRD * data source values, re-indexes them by data source id for threshold * evaluation, and hands them off to the threshold-checking logic * before/alongside the normal RRD update flow. Registered as a Cacti * poller output hook. * * @param array &$rrd_update_array The newly collected poller output * data, keyed by local_data_id. * * @return array The (possibly filtered/annotated) $rrd_update_array, * for hook chaining. * * @global array $config Cacti global configuration array; used to * locate library files to include. * @global bool $debug Whether debug output is enabled. */ function thold_poller_output(&$rrd_update_array) { global $config, $debug; include_once($config['base_path'] . '/plugins/thold/includes/functions.php'); include_once($config['library_path'] . '/snmp.php'); $rrd_reindexed = []; $rrd_time_reindexed = []; $local_data_ids = ''; foreach ($rrd_update_array as $item) { if (isset($item['times'][key($item['times'])])) { $local_data_ids .= ($local_data_ids != '' ? ', ' : '') . $item['local_data_id']; $rrd_reindexed[$item['local_data_id']] = $item['times'][key($item['times'])]; $rrd_time_reindexed[$item['local_data_id']] = key($item['times']); } } if ($local_data_ids != '') { if (read_config_option('thold_daemon_enable') == 'on') { // array_chunk() takes the size of each chunk, not how many to make. $rrd_update_array_chunks = array_chunk($rrd_update_array, 50, true); foreach ($rrd_update_array_chunks as $rrd_update_array_chunk) { $rrd_reindexed = []; $rrd_time_reindexed = []; $local_data_ids = ''; $thold_items = []; foreach ($rrd_update_array_chunk as $item) { if (isset($item['times'][key($item['times'])])) { $local_data_ids .= ($local_data_ids != '' ? ', ' : '') . $item['local_data_id']; $rrd_reindexed[$item['local_data_id']] = $item['times'][key($item['times'])]; $rrd_time_reindexed[$item['local_data_id']] = key($item['times']); } } if ($local_data_ids != '') { $thold_items = db_fetch_assoc("SELECT id, local_data_id, thread_id FROM thold_data WHERE thold_data.local_data_id IN ($local_data_ids)"); } if (cacti_sizeof($thold_items)) { /** * cache required polling data. prefer bulk inserts for * performance reasons - start with chunks of 1000 items */ $sql_max_inserts = 1000; $thold_items = array_chunk($thold_items, $sql_max_inserts); $sql_insert = 'INSERT INTO plugin_thold_daemon_data (poller_id, id, thread_id, rrd_reindexed, rrd_time_reindexed) VALUES '; foreach ($thold_items as $packet) { $sql_values = ''; foreach ($packet as $thold_item) { $sql_values .= ($sql_values != '' ? ', ' : '') . '(' . $config['poller_id'] . ', ' . $thold_item['id'] . ', ' . $thold_item['thread_id'] . ', ' . db_qstr(json_encode($rrd_reindexed[$thold_item['local_data_id']])) . ', ' . $rrd_time_reindexed[$thold_item['local_data_id']] . ')'; } db_execute($sql_insert . $sql_values); } } } return $rrd_update_array; } } else { return $rrd_update_array; } $tholds = db_fetch_assoc("SELECT td.id, td.name_cache AS thold_name, td.local_graph_id, td.percent_ds, td.expression, td.upper_ds, td.data_type, td.cdef, td.local_data_id, td.data_template_rrd_id, td.lastread, UNIX_TIMESTAMP(td.lasttime) AS lasttime, td.oldvalue, td.data_source_name AS name, dtr.data_source_type_id, dtd.rrd_step, dtr.rrd_maximum, dtr.rrd_heartbeat FROM thold_data AS td LEFT JOIN data_template_rrd AS dtr ON dtr.id = td.data_template_rrd_id LEFT JOIN data_template_data AS dtd ON dtd.local_data_id = td.local_data_id WHERE dtr.data_source_name != '' AND td.local_data_id IN($local_data_ids)"); if (cacti_sizeof($tholds)) { $sql = []; $status_sql = []; foreach ($tholds as $thold_data) { thold_debug("Checking Threshold: Name: '" . $thold_data['thold_name'] . "', Graph: '" . $thold_data['local_graph_id'] . "'"); $item = []; $currenttime = 0; $postvalue = thold_get_currentval($thold_data, $rrd_reindexed, $rrd_time_reindexed, $item, $currenttime); $currentval = $postvalue; switch ($thold_data['data_type']) { case 0: break; case 1: if ($thold_data['cdef'] != 0) { $currentval = thold_build_cdef($thold_data['cdef'], $postvalue, $thold_data['local_data_id'], $thold_data['data_template_rrd_id']); } break; case 2: if ($thold_data['percent_ds'] != '') { $currentval = thold_calculate_percent($thold_data, $postvalue, $rrd_reindexed); } break; case 3: if ($thold_data['expression'] != '') { $currentval = thold_calculate_expression($thold_data, $postvalue, $rrd_reindexed, $rrd_time_reindexed); } break; case 4: if ($thold_data['upper_ds'] != '') { $currentval = thold_calculate_lower_upper($thold_data, $postvalue, $rrd_reindexed); } break; } if (!is_numeric($currentval)) { if (read_config_option('thold_consider_unknown_zero') == 'on') { $normalized = strtolower($currentval); if ($normalized == 'u' || $normalized == 'nan' || $normalized == '') { // Fail closed: an unavailable sample must not be persisted as a // manufactured zero, or thold_check_threshold()'s unavailable-sample // guard never sees it and can alert/recover on the fabricated value. thold_debug('Threshold: ' . $thold_data['thold_name'] . ' unknown value would have been treated as zero; preserving unavailable state instead', 'thold'); if (read_config_option('thold_log_unknown_to_zero') == 'on') { cacti_log('NOTE: Threshold \'' . $thold_data['thold_name'] . '\' unknown value would have been treated as zero; preserving unavailable state instead', true, 'THOLD'); } } } $currentval = ''; } $sample_rows = thold_polling_sample_row($thold_data, $item, $currentval, $currenttime); if ($sample_rows['sample_row'] !== null) { $sql[] = $sample_rows['sample_row']; } elseif ($sample_rows['status_row'] !== null) { $status_sql[] = $sample_rows['status_row']; } } if (cacti_sizeof($sql)) { foreach (array_chunk($sql, 400) as $chunk) { $placeholders = implode(', ', array_fill(0, cacti_sizeof($chunk), '(?, ?, ?, FROM_UNIXTIME(?), ?)')); $params = []; foreach ($chunk as $row) { $params[] = $row['id']; $params[] = $row['tcheck']; $params[] = $row['lastread']; $params[] = $row['lasttime']; $params[] = $row['oldvalue']; } db_execute_prepared('INSERT INTO thold_data (id, tcheck, lastread, lasttime, oldvalue) VALUES ' . $placeholders . ' ON DUPLICATE KEY UPDATE tcheck = VALUES(tcheck), lastread = VALUES(lastread), lasttime = VALUES(lasttime), oldvalue = VALUES(oldvalue)', $params); } } if (cacti_sizeof($status_sql)) { foreach (array_chunk($status_sql, 400) as $chunk) { $placeholders = implode(', ', array_fill(0, cacti_sizeof($chunk), '(?, ?, ?)')); $params = []; foreach ($chunk as $row) { $params[] = $row['id']; $params[] = $row['tcheck']; $params[] = $row['lastread']; } db_execute_prepared('INSERT INTO thold_data (id, tcheck, lastread) VALUES ' . $placeholders . ' ON DUPLICATE KEY UPDATE tcheck = VALUES(tcheck), lastread = VALUES(lastread)', $params); } } thold_polling_cleanup(cacti_sizeof($sql) || cacti_sizeof($status_sql)); } return $rrd_update_array; } /** * Evaluates every configured threshold against its data source's * current/historical values (fetching remote-poller data directly from * the remote database when remote storage is configured), triggering * notifications/alerts for thresholds that have tripped or recovered. * Invoked once per poller cycle from the main threshold-checking flow. * * @return int The total number of thresholds evaluated. * * @global array $config Cacti global configuration array; used to * locate library files to include. */ function thold_check_all_thresholds() { global $config; include($config['base_path'] . '/plugins/thold/includes/arrays.php'); include_once($config['base_path'] . '/plugins/thold/includes/functions.php'); include_once($config['base_path'] . '/lib/time.php'); if (read_config_option('remote_storage_method') == 1) { if ($config['poller_id'] == 1) { $sql_query = "SELECT td.*, h.hostname, h.description, h.site_id, h.location, h.notes AS dnotes, h.snmp_engine_id FROM thold_data AS td LEFT JOIN host AS h ON h.id = td.host_id LEFT JOIN data_template_rrd AS dtr ON dtr.id = td.data_template_rrd_id WHERE thold_per_enabled = 'on' AND ( thold_template_id = 0 OR ( thold_template_id > 0 AND ( template_enabled != 'on' OR (template_enabled = 'on' AND thold_enabled = 'on') ) ) ) AND (h.poller_id = 1 OR h.poller_id IS NULL) AND td.tcheck = 1 AND h.status = 3"; } else { $sql_query = 'SELECT td.*, h.hostname, h.description, h.site_id, h.location, h.notes AS dnotes, h.snmp_engine_id FROM thold_data AS td LEFT JOIN host AS h ON h.id = td.host_id LEFT JOIN data_template_rrd AS dtr ON dtr.id = td.data_template_rrd_id WHERE h.poller_id = ' . $config['poller_id'] . " AND thold_per_enabled = 'on' AND ( thold_template_id = 0 OR ( thold_template_id > 0 AND ( template_enabled != 'on' OR (template_enabled = 'on' AND thold_enabled = 'on') ) ) ) AND td.tcheck = 1 AND h.status = 3"; } } else { $sql_query = "SELECT td.*, h.hostname, h.description, h.site_id, h.location, h.notes AS dnotes, h.snmp_engine_id FROM thold_data AS td LEFT JOIN data_template_rrd AS dtr ON dtr.id = td.data_template_rrd_id LEFT JOIN host as h ON td.host_id = h.id WHERE thold_per_enabled = 'on' AND ( thold_template_id = 0 OR ( thold_template_id > 0 AND ( template_enabled != 'on' OR (template_enabled = 'on' AND thold_enabled = 'on') ) ) ) AND td.tcheck = 1 AND h.status = 3"; } $tholds = api_plugin_hook_function('thold_get_live_hosts', db_fetch_assoc($sql_query)); $total_tholds = sizeof($tholds); $checked = []; foreach ($tholds as $thold) { thold_check_threshold($thold); $checked[] = (int) $thold['id']; } /* * Clear the dirty flag on what was evaluated, and nothing else. Clearing * by poller, or across the whole table, also cleared the thresholds whose * data arrived after the select above, so those readings were dropped * without ever being checked. The select already scopes to this poller, * so the identifiers carry that scoping with them. */ if (cacti_sizeof($checked)) { $placeholders = implode(', ', array_fill(0, cacti_sizeof($checked), '?')); db_execute_prepared('UPDATE thold_data SET tcheck = 0 WHERE id IN (' . $placeholders . ')', $checked); } return $total_tholds; } /** * Checks each monitored host's reachability status (including hosts * managed by the Maint plugin, when enabled) and records/clears dead- * host notification tracking rows, when the alert_deadnotify setting * is enabled; a no-op otherwise. * * @return int|null 0 immediately if dead-host notifications are * disabled; otherwise no explicit return value * (null). * * @global array $config Cacti global configuration array; used to * locate library files to include. */ function thold_update_host_status() { global $config; // Return if we aren't set to notify $deadnotify = (read_config_option('alert_deadnotify') == 'on'); if (!$deadnotify) { return 0; } include_once($config['base_path'] . '/plugins/thold/includes/functions.php'); include_once($config['library_path'] . '/snmp.php'); if (api_plugin_is_enabled('maint')) { if (file_exists($config['base_path'] . '/plugins/maint/functions.php')) { require_once($config['base_path'] . '/plugins/maint/functions.php'); } else if (file_exists($config['base_path'] . '/plugins/maint/includes/functions.php')) { require_once($config['base_path'] . '/plugins/maint/includes/functions.php'); } else { cacti_log('ERROR: cannot include maint functions', false, 'THOLD'); } } $ping_failure_count = read_config_option('ping_failure_count'); $queue = read_config_option('thold_notification_queue'); // Lets find hosts that were down, but are now back up if (read_config_option('remote_storage_method') == 1) { $failed = db_fetch_assoc_prepared('SELECT * FROM plugin_thold_host_failed WHERE poller_id = ?', [$config['poller_id']]); } else { $failed = db_fetch_assoc('SELECT * FROM plugin_thold_host_failed'); } if (cacti_sizeof($failed)) { foreach ($failed as $fh) { $alert_email = read_config_option('alert_email'); $host = db_fetch_row_prepared('SELECT * FROM host WHERE id = ?', [$fh['host_id']]); $notify_list_id = 0; if (!sizeof($host)) { db_execute_prepared('DELETE FROM plugin_thold_host_failed WHERE host_id = ?', [$fh['host_id']]); set_config_option('time_last_change_thold_device', time()); } elseif ($host['status'] == HOST_UP) { $snmp_system = ''; $snmp_hostname = ''; $snmp_location = ''; $snmp_contact = ''; $snmp_uptime = ''; $uptimelong = ''; $downtimemsg = ''; if (($host['snmp_community'] == '' && $host['snmp_username'] == '') || $host['snmp_version'] == 0) { // SNMP not in use $snmp_system = __('SNMP not in use', 'thold'); } else { $snmp_system = cacti_snmp_get($host['hostname'], $host['snmp_community'], '.1.3.6.1.2.1.1.1.0', $host['snmp_version'], $host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'], $host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_port'], $host['snmp_timeout'], read_config_option('snmp_retries'), SNMP_WEBUI); if (substr_count($snmp_system, '00:')) { $snmp_system = str_replace('00:', '', $snmp_system); $snmp_system = str_replace(':', ' ', $snmp_system); } if ($snmp_system != '') { $snmp_uptime = cacti_snmp_get($host['hostname'], $host['snmp_community'], '.1.3.6.1.6.3.10.2.1.3.0', $host['snmp_version'], $host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'], $host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_port'], $host['snmp_timeout'], read_config_option('snmp_retries'), SNMP_WEBUI); if (!is_numeric($snmp_uptime)) { $snmp_uptime = cacti_snmp_get($host['hostname'], $host['snmp_community'], '.1.3.6.1.2.1.1.3.0', $host['snmp_version'], $host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'], $host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_port'], $host['snmp_timeout'], read_config_option('snmp_retries'), SNMP_WEBUI); } else { $snmp_uptime *= 100; } $snmp_hostname = cacti_snmp_get($host['hostname'], $host['snmp_community'], '.1.3.6.1.2.1.1.5.0', $host['snmp_version'], $host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'], $host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_port'], $host['snmp_timeout'], read_config_option('snmp_retries'), SNMP_WEBUI); $snmp_location = cacti_snmp_get($host['hostname'], $host['snmp_community'], '.1.3.6.1.2.1.1.6.0', $host['snmp_version'], $host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'], $host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_port'], $host['snmp_timeout'], read_config_option('snmp_retries'), SNMP_WEBUI); $snmp_contact = cacti_snmp_get($host['hostname'], $host['snmp_community'], '.1.3.6.1.2.1.1.4.0', $host['snmp_version'], $host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'], $host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_port'], $host['snmp_timeout'], read_config_option('snmp_retries'), SNMP_WEBUI); if (is_numeric($snmp_uptime)) { $days = intval($snmp_uptime / (60 * 60 * 24 * 100)); $remainder = $snmp_uptime % (60 * 60 * 24 * 100); $hours = intval($remainder / (60 * 60 * 100)); $remainder = $remainder % (60 * 60 * 100); $minutes = intval($remainder / (60 * 100)); $uptimelong = $days . 'd ' . $hours . 'h ' . $minutes . 'm'; } else { $days = '0'; $remainder = '0'; $hours = '0'; $remainder = '0'; $minutes = '0'; $uptimelong = $days . 'd ' . $hours . 'h ' . $minutes . 'm'; } } } if (strtotime($host['status_fail_date']) > 192800) { $downtime = time() - strtotime($host['status_fail_date']); $downtime_days = floor($downtime / 86400); $downtime_hours = floor(($downtime - ($downtime_days * 86400)) / 3600); $downtime_minutes = floor(($downtime - ($downtime_days * 86400) - ($downtime_hours * 3600)) / 60); $downtime_seconds = $downtime - ($downtime_days * 86400) - ($downtime_hours * 3600) - ($downtime_minutes * 60); if ($downtime_days > 0) { $downtimemsg = $downtime_days . 'd ' . $downtime_hours . 'h ' . $downtime_minutes . 'm ' . $downtime_seconds . 's '; } elseif ($downtime_hours > 0) { $downtimemsg = $downtime_hours . 'h ' . $downtime_minutes . 'm ' . $downtime_seconds . 's'; } elseif ($downtime_minutes > 0) { $downtimemsg = $downtime_minutes . 'm ' . $downtime_seconds . 's'; } else { $downtimemsg = $downtime_seconds . 's '; } } else { $downtimemsg = __('N/A', 'thold'); } $subject = read_config_option('thold_up_subject'); if ($subject == '') { $subject = __('Devices Notice: () returned from DOWN state', 'thold'); } $site = db_fetch_cell_prepared('SELECT name FROM sites WHERE id = ?', [$host['site_id']]); if ($site == '') { $site = 'Unspecified'; } $subject = thold_str_replace('', $host['hostname'], $subject); $subject = thold_str_replace('', $host['description'], $subject); $subject = thold_str_replace('', $site, $subject); $subject = thold_str_replace('', $host['location'], $subject); $subject = thold_str_replace('', 'UP', $subject); $subject = thold_str_replace('', $downtimemsg, $subject); $subject = thold_str_replace('', $host['notes'], $subject); $subject = strip_tags($subject); $msg = read_config_option('thold_up_text'); if ($msg == '') { $msg = __('
System () status:

Site:
Location:
Current ping response: ms
Average system response : ms
System availability:
Total Checks Since Clear:
Total Failed Checks:
Last Date Checked UP:
Devices Previously DOWN for:

SNMP Info:
Name -
Location -
Uptime - ( ms)
System -

NOTE: ', 'thold'); } $msg = thold_str_replace('', $subject, $msg); $msg = thold_str_replace('', $host['hostname'], $msg); $msg = thold_str_replace('', $host['id'], $msg); $msg = thold_str_replace('', $host['description'], $msg); $msg = thold_str_replace('', $site, $msg); $msg = thold_str_replace('', $host['location'], $msg); $msg = thold_str_replace('', $snmp_uptime, $msg); $msg = thold_str_replace('', $uptimelong, $msg); $msg = thold_str_replace('