#!/usr/bin/env php 1) { print 'FATAL: This utility is designed for the main Data Collector only' . PHP_EOL; exit(1); } // process calling arguments $parms = $_SERVER['argv']; array_shift($parms); $debug = false; $force = false; $data_template_id = false; $prev_heartbeat = false; $new_heartbeat = false; global $heartbeats; if (cacti_sizeof($parms)) { foreach ($parms as $parameter) { if (str_contains($parameter, '=')) { [$arg, $value] = explode('=', $parameter, 2); } else { $arg = $parameter; $value = ''; } switch ($arg) { case '--new-heartbeat': $new_heartbeat = $value; break; case '--prev-heartbeat': $prev_heartbeat = $value; break; case '--data-template-id': $data_template_id = $value; break; case '--list-data-templates': $data_templates = db_fetch_assoc('SELECT id, name FROM data_template ORDER BY id'); if (cacti_sizeof($data_templates)) { print 'ID Data Template Name' . PHP_EOL; print '------ ----------------------' . PHP_EOL; foreach ($data_templates as $dt) { printf('%-6s %-25s' . PHP_EOL, $dt['id'], $dt['name']); } print PHP_EOL; } else { print 'WARNING: No Data Templates found!' . PHP_EOL; } exit(0); case '--list-heartbeats': if (cacti_sizeof($heartbeats)) { print 'Heartbeat Heartbeat Name' . PHP_EOL; print '--------- --------------' . PHP_EOL; foreach ($heartbeats as $index => $hb) { printf('%-12s%-17s' . PHP_EOL, $index, $hb); } print PHP_EOL; } else { print 'WARNING: No Heartbeats found!' . PHP_EOL; } exit(0); case '--list-profiles': $dspheartbeats = db_fetch_assoc('SELECT name, heartbeat FROM data_source_profiles ORDER BY heartbeat'); if (cacti_sizeof($dspheartbeats)) { print 'Heartbeat Data Source Profile' . PHP_EOL; print '--------- ----------------------' . PHP_EOL; foreach ($dspheartbeats as $hb) { printf('%-12s%-25s' . PHP_EOL, $hb['heartbeat'], $hb['name']); } print PHP_EOL; } else { print 'WARNING: No Data Source Profiles found!' . PHP_EOL; } exit(0); case '--debug': $debug = true; break; case '--force': $force = true; break; case '--version': case '-V': case '-v': display_version(); exit(0); case '--help': case '-H': case '-h': display_help(); exit(0); default: print 'ERROR: Invalid Parameter ' . $parameter . PHP_EOL . PHP_EOL; display_help(); exit(1); } } } else { print 'ERROR: You must supply input parameters' . PHP_EOL . PHP_EOL; display_help(); exit(1); } $poller_interval = read_config_option('poller_interval'); if ($poller_interval * 2.0 > $new_heartbeat) { printf('ERROR: Your new heartbeat must be greater or equal than %.0f' . PHP_EOL, $poller_interval * 2); exit(1); } $sql_where = ''; $sql_params = []; if ($data_template_id !== false && $data_template_id > 0) { $sql_params[] = $data_template_id; $sql_where = ' AND dtd.data_template_id = ?'; } elseif ($data_template_id !== false && ($data_template_id <= 0 || !is_numeric($data_template_id))) { printf('ERROR: Your Data Template ID \'%s\' is invalid' . PHP_EOL, $data_template_id); exit(1); } if ($new_heartbeat !== false && (!is_numeric($new_heartbeat) || $new_heartbeat < -1)) { printf('ERROR: Your New Heartbeat \'%s\' must be greater or equal to zero.' . PHP_EOL, $new_heartbeat); exit(1); } if (!array_key_exists($new_heartbeat, $heartbeats)) { printf(('ERROR: Your New Heartbeat \'%s\' is not a supported Heartbeat. Use ' . '--list-heartbeats to see the list.') . PHP_EOL, $new_heartbeat); exit(1); } if ($prev_heartbeat !== false && (!is_numeric($prev_heartbeat) || $prev_heartbeat < -1)) { printf('ERROR: Your Previous Heartbeat \'%s\' must be greater or equal to zero' . PHP_EOL, $prev_heartbeat); } elseif ($prev_heartbeat !== false) { $sql_where .= ' AND dtr.rrd_heartbeat = ?'; $sql_params[] = $prev_heartbeat; } $sql_params1 = array_merge([CACTI_PATH_RRA], $sql_params); $rrdfiles = db_fetch_assoc_prepared("SELECT dtr.local_data_id, dtd.name_cache, dt.name, REPLACE(dtd.data_source_path, '', ?) AS rrd, dtr.rrd_heartbeat, GROUP_CONCAT(DISTINCT dtr.data_source_name) AS data_sources FROM data_template_data AS dtd INNER JOIN data_template AS dt ON dtd.data_template_id = dt.id INNER JOIN data_template_rrd AS dtr ON dtd.local_data_id = dtr.local_data_id WHERE dtd.local_data_id > 0 $sql_where GROUP BY dtd.local_data_id", $sql_params1); $total_heartbeats = array_rekey($rrdfiles, 'rrd_heartbeat', 'rrd_heartbeat'); $profile_ids = db_fetch_assoc_prepared("SELECT DISTINCT dsp.id, dsp.heartbeat, dsp.name FROM data_template_data AS dtd INNER JOIN data_template_rrd AS dtr ON dtd.local_data_id = dtr.local_data_id INNER JOIN data_source_profiles AS dsp ON dsp.id = dtd.data_source_profile_id WHERE dtd.local_data_id > 0 $sql_where", $sql_params); if (cacti_sizeof($profile_ids) > 1) { printf('There are %s Data Source Proiles Impacted by this change.' . PHP_EOL, cacti_sizeof($profile_ids)); } elseif (cacti_sizeof($profile_ids) == 1) { printf('There is %s Data Source Proile Impacted by this change.' . PHP_EOL, cacti_sizeof($profile_ids)); } else { printf('ERROR: Not RRDfiles found that match your --prev-heartbeat or --data-template-id setting' . PHP_EOL); exit(1); } printf('Current Data Source Profile Heartbeats found are: %s' . PHP_EOL, implode(', ', $total_heartbeats)); printf('There will be %s RRDfiles updated as a result of this command.' . PHP_EOL, cacti_sizeof($rrdfiles)); if (!$force) { $exit = false; foreach ($profile_ids as $pid) { if ($pid['heartbeat'] != $new_heartbeat) { printf('ERROR: Data Source Profile \'%s\' has a heartbeat of %s which does' . PHP_EOL, $pid['name'], $pid['heartbeat']); printf(' not match the new heartbeat of %s. Use the --force option to update' . PHP_EOL, $new_heartbeat); printf(' the Data Source Profile\'s heartbeat as well as the Data Sources.' . PHP_EOL . PHP_EOL); $exit = true; } } if ($exit) { exit(1); } } else { printf('This is a forced run, impacted Data Source Profiles will have their Heartbeats updated as well' . PHP_EOL); } $i = 0; // keep the historical binary name when the setting has never been filled in $rrdtool_bin = read_config_option('path_rrdtool'); if ($rrdtool_bin == '') { $rrdtool_bin = 'rrdtool'; } if (cacti_sizeof($rrdfiles)) { foreach ($rrdfiles as $f) { if (file_exists($f['rrd'])) { $command = cacti_escapeshellcmd($rrdtool_bin) . ' tune ' . cacti_escapeshellarg($f['rrd']); $data_sources = explode(',', $f['data_sources']); foreach ($data_sources as $ds) { $command .= ' --heartbeat ' . cacti_escapeshellarg($ds . ':' . $new_heartbeat); } $output = []; $return_code = 0; debug(sprintf('Updating Heartbeat for Data Source:%s, Data Template:%s, RRD:%s from 600 to 900' . PHP_EOL, $f['name_cache'], $f['name'], $f['rrd'])); debug(sprintf("The RRDtool command is '$command'" . PHP_EOL)); $result = exec($command, $output, $return_code); if ($return_code != 0) { printf('Warning Error Occurred: ' . implode(', ', $output) . PHP_EOL); } else { db_execute_prepared('UPDATE data_template_rrd SET rrd_heartbeat = ? WHERE local_data_id = ?', [$new_heartbeat, $f['local_data_id']]); } } else { printf('WARNING: RRDfile \'%s\' does not exist!' . PHP_EOL, $f['rrd']); } $i++; if ($i % 100 == 0) { printf('Processed %s RRDfiles' . PHP_EOL, $i); } } printf('Processed a Total of %s RRDfiles' . PHP_EOL, $i); if ($data_template_id > 0) { db_execute_prepared('UPDATE data_template_rrd SET rrd_heartbeat = ? WHERE local_data_id = 0 AND data_template_id = ?', [$new_heartbeat, $data_template_id]); } if ($force) { foreach ($profile_ids as $pid) { db_execute_prepared('UPDATE data_source_profiles SET heartbeat = ? WHERE id = ?', [$new_heartbeat, $pid['id']]); } if ($data_template_id > 0) { db_execute_prepared('UPDATE data_template_rrd SET rrd_heartbeat = ? WHERE local_data_id = 0 AND data_template_id = ?', [$new_heartbeat, $data_template_id]); } else { db_execute_prepared('UPDATE data_template_rrd SET rrd_heartbeat = ? WHERE local_data_id = 0', [$new_heartbeat]); } } } function debug(string $message) : void { global $debug; if ($debug) { print 'DEBUG: ' . trim($message) . PHP_EOL; } } /** * display_version - displays version information * * @return void */ function display_version() : void { $version = get_cacti_cli_version(); print "Cacti Update RRDfile Heartbeat Utility, Version $version, " . COPYRIGHT_YEARS . PHP_EOL; } /** * display_help - displays the usage of the function * * @return void */ function display_help() : void { display_version(); print PHP_EOL; print ('usage: update_heartbeat.php --new-heartbeat=N [--data-template-id=id] ' . '[--prev-heartbeat=N] [--force] [--debug|-d]') . PHP_EOL . PHP_EOL; print 'A utility to update RRDfile heartbeats and the Cacti database to match.' . PHP_EOL . PHP_EOL; print 'Required:' . PHP_EOL; print (' --new-heartbeat=N - A Heartbeat in seconds. It must align ' . 'with available Heartbeats in Cacti') . PHP_EOL; print (' Currently Heartbeats include from ' . '20-172800 seconds. The value must also') . PHP_EOL; print ' be at least two times the current Cacti poller interval.' . PHP_EOL; print 'Optional:' . PHP_EOL; print (' --data-template-id=N - Only update Cacti Data Source Heartbeats ' . 'that are associated with a Data Template id.') . PHP_EOL; print (' --prev-heartbeat=N - Only update Cacti Data Sources that ' . 'currently have the Heartbeat specified.') . PHP_EOL; print (' --force - If the heartbeat selected does not match ' . 'the Data Source Profile, update the') . PHP_EOL; print (' Data Source Profile to match the command. ' . ' Otherwise, the script will exit.') . PHP_EOL; print ' --debug - Display verbose output during execution' . PHP_EOL . PHP_EOL; print 'List Options:' . PHP_EOL; print ' --list-data-templates - List all Data Templates' . PHP_EOL; print ' --list-heartbeats - List all supported Heartbeats' . PHP_EOL; print ' --list-profiles - List all Data Source Profiles and their Heartbeats' . PHP_EOL . PHP_EOL; }