#!/usr/bin/env php 1 && CACTI_CONNECTION === 'online') { $poller_db_cnn_id = $remote_db_cnn_id; } else { $poller_db_cnn_id = false; } // process calling arguments $parms = $_SERVER['argv']; array_shift($parms); if (cacti_sizeof($parms)) { foreach ($parms as $parameter) { if (str_contains($parameter, '=')) { [$arg, $value] = explode('=', $parameter, 2); } else { $arg = $parameter; $value = ''; } switch ($arg) { case '--version': case '-V': display_version(); exit(0); case '-H': case '--help': display_help(); exit(0); case '--poller': case '-p': $poller_id = $value; break; case '--child': case '-c': $host_id = $value; $type = 'child'; break; case '-t': case '--threads': $threads = intval($value); break; case '--debug': case '-d': $debug = true; break; default: print "ERROR: Invalid Argument: ($arg)" . PHP_EOL . PHP_EOL; display_help(); exit(1); } } } if ($debug) { $verbosity = POLLER_VERBOSITY_LOW; } else { $verbosity = POLLER_VERBOSITY_MEDIUM; } /** * Types include * * master - the main process launched from the Cacti main poller and will launch child processes * child - a child of the master process from the 'master' * */ // install signal handlers for UNIX only if (function_exists('pcntl_signal')) { pcntl_signal(SIGTERM, 'sig_handler'); pcntl_signal(SIGINT, 'sig_handler'); } // Record Start Time $start = microtime(true); // Get the process timeout value $timeout = poller_commands_timeout(); // send a gentle message to the log and stdout commands_debug('Polling Starting'); if ($host_id === false) { $hosts = array_rekey( db_fetch_assoc_prepared('SELECT DISTINCT SUBSTRING_INDEX(command, ":", 1) AS host_id FROM poller_command WHERE poller_id = ?', [$poller_id], true, $poller_db_cnn_id), 'host_id', 'host_id' ); if (cacti_sizeof($hosts)) { /** * Register the master process */ if (!register_process_start('commands', 'master', $poller_id, $timeout)) { exit(0); } // Master processing commands_master_handler($forcerun, $hosts, $threads); // take time to log performance data $recache = microtime(true); $recache_stats = sprintf('Poller:%s RecacheTime:%01.4f DevicesRecached:%s', $poller_id, round($recache - $start, 4), cacti_sizeof($hosts)); if (cacti_sizeof($hosts)) { cacti_log('STATS: ' . $recache_stats, true, 'RECACHE'); } // insert poller stats into the settings table db_execute_prepared('REPLACE INTO settings (name, value) VALUES (?, ?)', ['stats_recache_' . $poller_id, $recache_stats], true, $poller_db_cnn_id); unregister_process('commands', 'master', $poller_id); } else { cacti_log('NOTE: No Poller Commands found for processing', true, 'PCOMMAND', $verbosity); } } else { /** * Register the child process */ if (!register_process_start('commands', 'child', $host_id + 1000, $timeout)) { exit(0); } $last_host_id = 0; $first_host = true; /** * We will only remove records earlier than this date */ $max_updated = db_fetch_cell_prepared('SELECT MAX(UNIX_TIMESTAMP(last_updated)) FROM poller_command WHERE poller_id = ? AND SUBSTRING_INDEX(command, ":", 1) = ?', [$poller_id, $host_id], '', true, $poller_db_cnn_id); /** * Get the poller command records for the host */ $poller_commands = db_fetch_assoc_prepared('SELECT action, command, SUBSTRING_INDEX(command, ":", 1) AS host_id FROM poller_command WHERE poller_id = ? AND last_updated <= FROM_UNIXTIME(?) AND SUBSTRING_INDEX(command, ":", 1) = ?', [$poller_id, $max_updated, $host_id], true, $poller_db_cnn_id); if (cacti_sizeof($poller_commands)) { foreach ($poller_commands as $command) { switch ($command['action']) { case POLLER_COMMAND_REINDEX: [$device_id, $data_query_id] = explode(':', $command['command']); $device_id = intval($device_id); $data_query_id = intval($data_query_id); if ($last_host_id != $device_id) { $last_host_id = $device_id; $first_host = true; } else { $first_host = false; } if ($first_host) { cacti_log("Device[$device_id] NOTE: Recache Event Detected for Device", true, 'PCOMMAND'); } cacti_log("Device[$device_id] DQ[$data_query_id] RECACHE: Recache for Device started.", true, 'PCOMMAND', $verbosity); run_data_query($device_id, $data_query_id); cacti_log("Device[$device_id] DQ[$data_query_id] RECACHE: Recached successfully.", true, 'PCOMMAND', $verbosity); break; case POLLER_COMMAND_PURGE: $device_id = $command['command']; api_device_purge_from_remote($device_id, $poller_id); cacti_log("Device[$device_id] PURGE: Purged successfully.", true, 'PCOMMAND', $verbosity); break; default: cacti_log('ERROR: Unknown poller command issued', true, 'PCOMMAND'); } // record current_time $current = microtime(true); // end if runtime has been exceeded if (($current - $start) > MAX_RECACHE_RUNTIME) { cacti_log("ERROR: Poller Command processing timed out after processing '$command'", true, 'PCOMMAND'); break; } } db_execute_prepared('DELETE FROM poller_command WHERE poller_id = ? AND SUBSTRING_INDEX(command, ":", 1) = ? AND last_updated <= FROM_UNIXTIME(?)', [$poller_id, $host_id, $max_updated], true, $poller_db_cnn_id); } unregister_process('commands', 'child', $host_id + 1000); } function commands_master_handler(bool $forcerun, array &$hosts, int $threads) : void { commands_debug('There are ' . cacti_sizeof($hosts) . ' to reindex'); foreach ($hosts as $id) { // run the daily stats commands_debug("Launching Host ID $id"); commands_launch_child($id); // Wait for if there are 50 processes running while (true) { $running = commands_processes_running(); if ($running >= $threads) { commands_debug(sprintf('%s Processes Running, Sleeping for 2 seconds.', $running)); sleep(2); } else { commands_debug(sprintf('%s Processes Running, Launching more processes.', $running)); usleep(500000); break; } } } $starting = true; while (true) { if ($starting) { sleep(5); $starting = false; } $running = commands_processes_running(); if ($running > 0) { commands_debug(sprintf('%s Processes Running, Sleeping for 2 seconds.', $running)); sleep(2); } else { break; } } } /** * commands_launch_child - this function will launch collector children based upon * the maximum number of threads and the process type. * * @param int $host_id The Cacti host_id * * @return void */ function commands_launch_child(int $host_id) : void { global $seebug; $php_binary = read_config_option('path_php_binary'); commands_debug(sprintf('Launching Commands Process Number %s for Type %s', $host_id, 'child')); cacti_log(sprintf('NOTE: Launching Commands Process Number %s for Type %s', $host_id, 'child'), false, 'CLEANUP', POLLER_VERBOSITY_MEDIUM); exec_background($php_binary, CACTI_PATH_BASE . "/poller_commands.php --child=$host_id" . ($seebug ? ' --debug' : '')); } /** * commands_processes_running - given a type, determine the number * of sub-type or children that are currently running. * * @return int The number of running processes */ function commands_processes_running() : int { $running = db_fetch_cell('SELECT COUNT(*) FROM processes WHERE tasktype = "commands" AND taskname = "child"'); if ($running == 0) { return 0; } return $running; } /** * commands_debug - this simple routine prints a standard message to the console * when running in debug mode. * * @param string $message The message to display * * @return void */ function commands_debug(string $message) : void { global $seebug; if ($seebug) { print 'COMMANDS: ' . $message . PHP_EOL; } } /** * sig_handler - provides a generic means to catch exceptions to the Cacti log. * * @param int $signo The signal that was thrown by the interface. * * @return void */ function sig_handler(int $signo) : void { global $type, $host_id, $poller_id; switch ($signo) { case SIGTERM: case SIGINT: cacti_log('WARNING: RRDfile Cleanup Poller terminated by user', false, 'CLEANUP'); if (str_contains($type, 'master')) { commands_kill_running_processes(); } if ($type == 'master') { unregister_process('commands', $type, $poller_id, (int) getmypid()); } else { unregister_process('commands', $type, $host_id + 1000, (int) getmypid()); } exit(1); default: // ignore all other signals } } /** * commands_kill_running_processes - this function is part of an interrupt * handler to kill children processes when the parent is killed * * @return void */ function commands_kill_running_processes() : void { global $type; $processes = db_fetch_assoc_prepared('SELECT * FROM processes WHERE tasktype = "commands" AND taskname IN ("child") AND pid != ?', [getmypid()]); if (cacti_sizeof($processes)) { foreach ($processes as $p) { if (cacti_process_still_running((int) $p['pid'])) { cacti_log(sprintf('WARNING: Killing Commands %s PID %d due to another due to signal or overrun.', ucfirst($p['taskname']), $p['pid']), false, 'CLEANUP'); cacti_process_kill((int) $p['pid'], SIGTERM, 'CLEANUP'); } unregister_process($p['tasktype'], $p['taskname'], $p['taskid'], $p['pid']); } } } function poller_commands_timeout() : int { $timeout = intval(read_config_option('commands_timeout')); if (empty($timeout)) { $timeout = 300; } return $timeout; } /** * display_version - displays version information * * @return void */ function display_version() : void { $version = get_cacti_cli_version(); print "Cacti Poller Commands Poller, Version $version " . COPYRIGHT_YEARS . PHP_EOL; } /** * display_help - displays help information * * @return void */ function display_help() : void { display_version(); print PHP_EOL; print 'usage: poller_commands.php [--poller=ID] [--debug]' . PHP_EOL . PHP_EOL; print 'Cacti\'s Commands Poller. This poller can receive specifically crafted commands from' . PHP_EOL; print 'either the Cacti UI, or from the main poller, and then run them in the background.' . PHP_EOL . PHP_EOL; print 'Optional:' . PHP_EOL; print ' --poller=ID - The poller to run as. Defaults to the system poller' . PHP_EOL; print ' --threads=N - Override the System Processes setting and use N processes' . PHP_EOL; print ' --debug - Display verbose output during execution' . PHP_EOL . PHP_EOL; }