#!/usr/bin/env php 0, 'folders' => 1, 'content' => Array ( 2446 => Array ( 'files' => 1, 'folders' => 0, 'content' => Array ( 0 => '42194.rrd', ) ) ) ); */ $known_cfs = [1 => 'AVERAGE', 2 => 'MIN', 3 => 'MAX', 4 => 'LAST']; $known_ds_types = [1 => 'GAUGE', 2 => 'COUNTER', 3 => 'DERIVE', 4 => 'ABSOLUTE']; if ($scanned_directory['folders']) { // create backup folder if (!mkdir((string) $tmp_backup_folder)) { print 'ERROR: Unable to create a backup folder!' . PHP_EOL; exit(1); } // create log file $log_handle = fopen($tmp_backup_folder . 'tmp_rrdresize.log', 'w'); f_notify('Boost Plugin Status', ($boost_enabled ? 'enabled' : 'disabled')); f_log('Boost Plugin Status ' . ($boost_enabled ? 'enabled' : 'disabled')); f_notify('Boost Server Status', ($boost_server_enabled ? 'enabled' : 'disabled')); f_log('Boost Server Status ' . ($boost_server_enabled ? 'enabled' : 'disabled')); f_log('Total number of RRD files found: ' . $total_files); // generate a list of hosts using this data template id $db_hosts = db_fetch_assoc_prepared('SELECT DISTINCT host_id FROM data_local WHERE data_template_id = ?', [$data_template_id] ); $dt_hosts = []; if (cacti_sizeof($db_hosts) > 0) { foreach ($db_hosts as $key => $value) { $dt_hosts[] = $value['host_id']; } } else { print 'No hosts found.' . PHP_EOL; exit(1); } $db_data_sources = db_fetch_assoc_prepared('SELECT DISTINCT local_data_id FROM data_template_data WHERE data_template_id = ? AND data_source_path IS NOT NULL', [$data_template_id] ); $dt_data_sources = []; if (cacti_sizeof($db_data_sources) > 0) { foreach ($db_data_sources as $key => $value) { $dt_data_sources[] = $value['local_data_id']; } } else { print 'No data sources found.' . PHP_EOL; exit(1); } $subfolders = $scanned_directory['content']; $counter = 0; foreach ($subfolders as $host_id => $host_data) { if (!in_array($host_id, $dt_hosts, true)) { continue; } if ($host_data['files']) { $host_files = $host_data['content']; foreach ($host_files as $host_file) { $local_data_id = intval(substr($host_file, 0, -4)); if (!in_array($local_data_id, $dt_data_sources, true)) { continue; } // Data Template Data $file = $path_rra . DIRECTORY_SEPARATOR . $host_id . DIRECTORY_SEPARATOR . $host_file; if (!$local_data_id) { f_log('[ERROR] Unable to detect local data id: ' . $file); $total_errors++; continue; } if (!is_readable($file)) { f_log('[ERROR] : Read permissions required for file: ' . $file); $total_errors++; continue; } if (!is_resource_writable($file)) { f_log('[ERROR] : Write permissions required for file: ' . $file); $total_errors++; continue; } $search_pattern = '/' . $host_id . '/' . $host_file; $local_data = db_fetch_row_prepared('SELECT * FROM data_template_data WHERE data_source_path = ?', [$search_pattern]); if ($local_data) { if ($local_data['data_template_id'] != $data_template_id) { // this file has not to be update due to template mismatches f_log('[SKIPPED] Template mismatch: ' . $file); continue; } f_notify('File', $file); f_notify('Local Data ID', $local_data_id); f_notify('Cacti Settings '); // grep all data template settings $data_template_ds = db_fetch_assoc_prepared("SELECT id, rrd_maximum, rrd_minimum, rrd_heartbeat, data_source_type_id, data_source_name FROM data_template_rrd WHERE hash != '' AND data_template_id = ? ORDER BY id", [$local_data['data_template_id']] ); $data_template_ds_counter = cacti_sizeof($data_template_ds); $data_template_data_settings = db_fetch_assoc_prepared('SELECT * FROM data_template_data_rra LEFT JOIN rra ON rra.id = data_template_data_rra.rra_id RIGHT JOIN rra_cf ON rra_cf.rra_id = rra.id WHERE data_template_data_rra.data_template_data_id = ?', [$local_data['id']] ); f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); } else { f_log('[OUTDATED] Unable to detect referenced local data id: ' . $file); continue; } // BOOST update process f_notify('Boost update'); if ($boost_enabled && !$dry_run_mode) { $boost_update = true; // placeholder // ----- run on demand update if Boost is enabled and cached data is part of the report period ----- $output = boost_process_poller_output($local_data_id); f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); } else { f_notify(false, "\033[0;36m[SKIPPED]\033[0m"); } f_notify('RRDtool info'); $output = rrdtool_pipe_execute(' info ' . $file . "\r\n", $rrdtool_pipes); if (str_contains($output, 'ERROR')) { f_notify(false, "\033[0;31m[FAILED]\033[0m"); f_log('[ERROR] Unable to fetch RRDtool Info: ' . $file); continue; } if ($output == '') { f_notify(false, "\033[0;31m[FAILED]\033[0m"); f_log('[ERROR] Unable to fetch RRDtool Info: ' . $file); continue; } else { $rrd_info = rrdtool_parse_info($output); if ($rrd_info) { f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); } else { f_notify(false, "\033[0;31m[FAILED]\033[0m"); f_log('[ERROR] Unable to parse RRDtool Info: ' . $file); continue; } // Analyze Data Structure f_notify('DS Structure'); $ds_mismatch = false; $tmp_ds = $rrd_info['ds']; $ds_mismatch_fixable = false; $ds_error = false; foreach ($data_template_ds as $data_template__ds_settings) { $found = false; $defined_ds_name = $data_template__ds_settings['data_source_name']; $defined_ds_type = $known_ds_types[$data_template__ds_settings['data_source_type_id']]; $defined_minimum = is_numeric($data_template__ds_settings['rrd_minimum']) ? $data_template__ds_settings['rrd_minimum'] : 'NaN'; $defined_maximum = is_numeric($data_template__ds_settings['rrd_maximum']) ? $data_template__ds_settings['rrd_maximum'] : 'NaN'; $defined_min_heartbeat = $data_template__ds_settings['rrd_heartbeat']; if (isset($tmp_ds[$defined_ds_name])) { $tmp_ds_rrd = $tmp_ds[$defined_ds_name]; if ($tmp_ds_rrd['type'] == $defined_ds_type && $tmp_ds_rrd['minimal_heartbeat'] == $defined_min_heartbeat && $tmp_ds_rrd['min'] == $defined_minimum && $tmp_ds_rrd['max'] == $defined_maximum) { $found = true; } else { $ds_mismatch = true; $ds_error = 'definition'; $ds_mismatch_fixable = ($tmp_ds_rrd['type'] == $defined_ds_type) ? true : false; if ($tmp_ds_rrd['minimal_heartbeat'] != $defined_min_heartbeat) { $rrd_info['ds'][$defined_ds_name]['minimal_heartbeat'] = $defined_min_heartbeat; f_log('[NOTICE] Data Source \'' . $defined_ds_name . '\' heartbeat mismatch: ' . $file); } if ($tmp_ds_rrd['min'] != $defined_minimum) { $rrd_info['ds'][$defined_ds_name]['min'] = $defined_minimum; f_log('[NOTICE] Data Source \'' . $defined_ds_name . '\' minimum mismatch: ' . $file); } if ($tmp_ds_rrd['max'] != $defined_maximum) { $rrd_info['ds'][$defined_ds_name]['max'] = $defined_maximum; f_log('[NOTICE] Data Source \'' . $defined_ds_name . '\' maximum mismatch: ' . $file); } } unset($tmp_ds[$defined_ds_name]); } else { $ds_mismatch = true; $ds_error = 'missing'; break; } } $ds_superfluous_ids = []; if ($ds_mismatch == true) { if ($ds_error == 'missing') { f_notify(false, "\033[0;31m[COUNT - MISSING DS]\033[0m"); f_log('[ERROR] Data source missing: ' . $file); continue; } if ($ds_error == 'definition') { if (!$ds_mismatch_fixable) { f_notify(false, "\033[0;31m[UNFIXABLE]\033[0m"); f_log('[ERROR] Unable to fix misconfigured data source: ' . $file); continue; } else { f_notify(false, "\033[0;35m[FIXABLE]\033[0m"); } } } else { f_notify(false, "\033[0;32m[MATCH]\033[0m"); } if (!empty($tmp_ds)) { // Additional ds found ! :| $ds_names = array_keys($rrd_info['ds']); foreach ($tmp_ds as $tmp_ds_name => $tmp_ds_settings) { $ds_superfluous_ids += array_keys($ds_names, $tmp_ds_name, true); } f_notify('DS COUNT'); f_notify(false, "\033[0;35m[MISMATCH]\033[0m"); f_log('[NOTICE] Superfluous data sources \'' . implode(',', $ds_superfluous_ids) . '\' found: ' . $file); } else { f_notify('DS COUNT'); f_notify(false, "\033[0;32m[MATCH]\033[0m"); } f_notify('RRA Structure'); $rra_mismatch = false; $tmp_rras = $rrd_info['rra']; foreach ($data_template_data_settings as $data_template__rra_settings) { // rra exists - let us compare the details $found = false; $defined_cf = $known_cfs[$data_template__rra_settings['consolidation_function_id']]; $defined_rows = $data_template__rra_settings['rows']; $defined_xff = $data_template__rra_settings['x_files_factor']; $defined_ppr = $data_template__rra_settings['steps']; foreach ($tmp_rras as $tmp_rra) { if ($tmp_rra['cf'] == $defined_cf && $tmp_rra['rows'] == $defined_rows && $tmp_rra['xff'] == $defined_xff && $tmp_rra['pdp_per_row'] == $defined_ppr) { $found = true; break; } } if (!$found) { $rra_mismatch = true; break; } } if ($rra_mismatch == false) { f_notify(false, "\033[0;32m[MATCH]\033[0m"); } else { f_notify(false, "\033[0;35m[MISMATCH]\033[0m"); } } if ($rra_mismatch == false & $ds_mismatch == false) { f_notify('Update', "\033[0;35m[NOT NECESSARY]\033[0m"); continue; } f_notify('RRDtool dump'); $file_dump = rrdtool_pipe_execute(' dump ' . $file . "\r\n", $rrdtool_pipes); if (str_contains($file_dump, 'ERROR')) { f_notify(false, "\033[0;31m[FAILED]\033[0m"); // ## log_missing continue; } else { $rrd_data = json_decode((string) json_encode(simplexml_load_string($file_dump)), true); if ($rrd_data === false) { f_notify(false, "\033[0;31m[FAILED]\033[0m"); // ## log_missing continue; } else { f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); } } // Re-configuration process starts f_notify('Rebuilding RRAs'); $rra_timespans = []; $tmp_step = $rrd_info['step']; foreach ($tmp_rras as $rra_index => $rra_settings) { $rra_cf = $rra_settings['cf']; $step = $rra_settings['pdp_per_row'] * $tmp_step; $end = $rrd_info['last_update'] - $rrd_info['last_update'] % ($tmp_step * $rra_settings['pdp_per_row']); $start = $end - ($rra_settings['rows'] - 1) * $step; $rra_timespans[$rra_settings['cf']][$rra_index] = [ 'step' => $step, 'start' => $start, 'end' => $end ]; } $rrd_new_header = '' . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL . "\t0003" . PHP_EOL . "\t300" . PHP_EOL . "\t" . $rrd_info['last_update'] . '' . PHP_EOL; $rrd_new_ds_definition = ''; $i = 0; foreach ($rrd_info['ds'] as $rrd_info_ds_name => $rrd_info_ds_settings) { if (!in_array($i, $ds_superfluous_ids, true)) { $rrd_new_ds_definition__ds_definition_template = "\t" . PHP_EOL . "\t\t " . $rrd_info_ds_name . ' ' . PHP_EOL . "\t\t " . $rrd_info_ds_settings['type'] . ' ' . PHP_EOL . "\t\t" . $rrd_info_ds_settings['minimal_heartbeat'] . '' . PHP_EOL . "\t\t" . $rrd_info_ds_settings['min'] . '' . PHP_EOL . "\t\t" . $rrd_info_ds_settings['max'] . '' . PHP_EOL . "\t\t" . $rrd_info_ds_settings['last_ds'] . '' . PHP_EOL . "\t\t" . $rrd_info_ds_settings['value'] . '' . PHP_EOL . "\t\t " . $rrd_info_ds_settings['unknown_sec'] . ' ' . PHP_EOL . "\t" . PHP_EOL; $rrd_new_ds_definition .= $rrd_new_ds_definition__ds_definition_template; } $i++; } $rrd_new_header .= $rrd_new_ds_definition; $rrd_new_body = "\t" . PHP_EOL; $rrd_new_body__ds_definition = ''; // create one fake entry to fill up gaps we do not have data for if ($data_template_ds_counter == 1) { $row_copy_fake = 'NaN'; } else { $row_copy_fake = []; for ($i = 0; $i < $data_template_ds_counter; $i++) { $row_copy_fake[] = 'NaN'; } } // create new archives uses the data template definitions provided by Cacti and fill them up with existing data foreach ($data_template_data_settings as $data_template__rra_settings) { $defined_cf = $known_cfs[$data_template__rra_settings['consolidation_function_id']]; $defined_rows = $data_template__rra_settings['rows']; $defined_xff = $data_template__rra_settings['x_files_factor']; $defined_ppr = $data_template__rra_settings['steps']; $defined_step = $data_template__rra_settings['steps'] * $rrd_info['step']; // $defined_timespan = $data_template__rra_settings['timespan']; $defined_timespan = $defined_rows * $defined_step; $last_data_point = $rrd_info['last_update'] - $rrd_info['last_update'] % $defined_step; $first_data_point = $last_data_point - $defined_timespan + $defined_step; $timestamps = array_flip(range($first_data_point, $last_data_point, $defined_step)); $rrd_new_body .= "\t" . PHP_EOL . "\t\t" . $defined_cf . '' . PHP_EOL . "\t\t" . $defined_ppr . '' . PHP_EOL . PHP_EOL . "\t\t" . PHP_EOL . "\t\t" . sprintf('%e', $defined_xff) . '' . PHP_EOL . "\t\t" . PHP_EOL . "\t\t" . PHP_EOL; $rrd_new_body__ds_definition = ''; for ($i = 0; $i < $data_template_ds_counter; $i++) { $rrd_new_body__ds_definition_template = "\t\t\t" . PHP_EOL . "\t\t\t__placeholder__$i" . PHP_EOL . "\t\t\tNaN" . PHP_EOL . "\t\t\tNaN" . PHP_EOL . "\t\t\t0" . PHP_EOL . "\t\t\t" . PHP_EOL; $rrd_new_body__ds_definition .= $rrd_new_body__ds_definition_template; } $rrd_new_body .= $rrd_new_body__ds_definition . "\t\t" . PHP_EOL . "\t\t" . PHP_EOL; // initialize globals $selected_archive_index = false; $last_values = false; $consolidation_required = false; foreach ($timestamps as $timestamp => $dummy) { $step = 9999999999; $selected_archive_index = false; foreach ($rra_timespans[$defined_cf] as $g_rra_index => $g_rra_settings) { if ($g_rra_settings['step'] > $defined_step) { if (($timestamp > $g_rra_settings['start']) && ($timestamp <= $g_rra_settings['end']) && ($g_rra_settings['step'] <= $step)) { $consolidation_required = false; $step = $g_rra_settings['step']; $selected_archive_index = $g_rra_index; } } elseif ($g_rra_settings['step'] < $defined_step) { if ((($timestamp - $defined_step) >= $g_rra_settings['start']) && ($timestamp <= $g_rra_settings['end']) && ($g_rra_settings['step'] <= $step)) { $consolidation_required = true; $step = $g_rra_settings['step']; $selected_archive_index = $g_rra_index; } } else { if (($timestamp > $g_rra_settings['start']) && ($timestamp <= $g_rra_settings['end'])) { $consolidation_required = false; $step = $g_rra_settings['step']; $selected_archive_index = $g_rra_index; break; } } } // There's no data available we could reuse. Fill these entries with NaNs and jump to the next one if ($selected_archive_index === false) { $rrd_new_body .= "\t\t\t ' . (is_array($row_copy_fake) ? implode('', $row_copy_fake) : $row_copy_fake) . '' . PHP_EOL; continue; } $g_rra_settings = $rra_timespans[$defined_cf][$selected_archive_index]; if (!$consolidation_required) { // calculate the correct index number of the selected archive $calculated_index = ($timestamp - $timestamp % $g_rra_settings['step'] - $g_rra_settings['start'] - $defined_step) / $g_rra_settings['step']; if ($g_rra_settings['step'] > $defined_step && ($timestamp % $g_rra_settings['step']) == 0) { $calculated_index--; } if (isset($rrd_data['rra'][$selected_archive_index]['database']['row'][$calculated_index]['v'])) { $row_copy = $rrd_data['rra'][$selected_archive_index]['database']['row'][$calculated_index]['v']; if (!empty($ds_superfluous_ids)) { foreach ($ds_superfluous_ids as $ds_superfluous_id) { unset($row_copy[$ds_superfluous_id]); } } $rrd_new_body .= "\t\t\t ' . (is_array($row_copy) ? implode('', $row_copy) : $row_copy) . '' . PHP_EOL; } else { f_notify(false, "\033[0;31m[FAILED]\033[0m"); print_r($rra_timespans); print $timestamp . '::' . $defined_cf; // print $rrd_new_body; // / log_missing continue 4; } } else { // data consolidation required, because we will start grepping data from an archive with a higher granularity // calculate the correct index number of the selected archive $calculated_index = ($timestamp - $timestamp % $g_rra_settings['step'] - $g_rra_settings['start'] - $defined_step) / $g_rra_settings['step']; $high_granulary_rows_required = round($defined_step / $g_rra_settings['step'], 0); $consolidated_ds_values = []; if (is_array($rrd_data['rra'][$selected_archive_index]['database']['row'][$calculated_index]['v'])) { foreach ($rrd_data['rra'][$selected_archive_index]['database']['row'][$calculated_index]['v'] as $ds_index => $ds_value) { $unconsolidated_ds_values = []; for ($i = 0; $i < $high_granulary_rows_required; $i++) { $unconsolidated_ds_values[] = $rrd_data['rra'][$selected_archive_index]['database']['row'][$calculated_index + $i]['v'][$ds_index]; } foreach ($unconsolidated_ds_values as $key => $value) { if (!is_numeric($value) | is_null($value)) { unset($unconsolidated_ds_values[$key]); } } if (count($unconsolidated_ds_values) > 0) { // drop all NaNs switch ($defined_cf) { case 'AVERAGE': $consolidated_value = cacti_sizeof($unconsolidated_ds_values) ? 'NaN' : array_sum($unconsolidated_ds_values) / $high_granulary_rows_required; break; case 'MAX': $consolidated_value = cacti_sizeof($unconsolidated_ds_values) ? 'NaN' : max($unconsolidated_ds_values); break; case 'MIN': $consolidated_value = cacti_sizeof($unconsolidated_ds_values) ? 'NaN' : min($unconsolidated_ds_values); break; case 'LAST': $consolidated_value = cacti_sizeof($unconsolidated_ds_values) ? 'NaN' : end($unconsolidated_ds_values); break; } $consolidated_ds_values[$ds_index] = preg_replace('/(e[+-])(\d)$/', '${1}0$2', sprintf('%.10e', $consolidated_value)); } else { $consolidated_ds_values[$ds_index] = 'NaN'; } } } else { $unconsolidated_ds_values = []; for ($i = 0; $i < $high_granulary_rows_required; $i++) { $unconsolidated_ds_values[] = $rrd_data['rra'][$selected_archive_index]['database']['row'][$calculated_index + $i]['v']; } foreach ($unconsolidated_ds_values as $key => $value) { if (!is_numeric($value) | is_null($value)) { unset($unconsolidated_ds_values[$key]); } } if (count($unconsolidated_ds_values) > 0) { // drop all NaNs switch ($defined_cf) { case 'AVERAGE': $consolidated_value = cacti_sizeof($unconsolidated_ds_values) > 0 ? 'NaN' : array_sum($unconsolidated_ds_values) / $high_granulary_rows_required; break; case 'MAX': $consolidated_value = cacti_sizeof($unconsolidated_ds_values) > 0 ? 'NaN' : max($unconsolidated_ds_values); break; case 'MIN': $consolidated_value = cacti_sizeof($unconsolidated_ds_values) > 0 ? 'NaN' : min($unconsolidated_ds_values); break; case 'LAST': $consolidated_value = cacti_sizeof($unconsolidated_ds_values) > 0 ? 'NaN' : end($unconsolidated_ds_values); break; } $consolidated_ds_values[0] = preg_replace('/(e[+-])(\d)$/', '${1}0$2', sprintf('%.10e', $consolidated_value)); } else { $consolidated_ds_values[0] = 'NaN'; } } if (!empty($ds_superfluous_ids)) { foreach ($ds_superfluous_ids as $ds_superfluous_id) { unset($consolidated_ds_values[$ds_superfluous_id]); } } $rrd_new_body .= "\t\t\t ' . implode('', $consolidated_ds_values) . '' . PHP_EOL; } } // return the last identified value if ($selected_archive_index !== false) { $last_values = (!isset($calculated_index)) ? 'NaN' : $rrd_data['rra'][$selected_archive_index]['database']['row'][$calculated_index]['v']; } if (is_array($last_values)) { foreach ($last_values as $index => $value) { $rrd_new_body = str_replace('>__placeholder__' . $index . '<', '>' . $value . '<', $rrd_new_body); } } else { $rrd_new_body = str_replace('>__placeholder__0<', '>' . $last_values . '<', $rrd_new_body); } $rrd_new_body .= "\t\t" . PHP_EOL . "\t" . PHP_EOL; } f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); // update temporary XML file f_notify('Create Temp File'); if (!file_put_contents($tmp_xml_file, $rrd_new_header . $rrd_new_body . '')) { // Critical ERROR - This should never happen f_notify(false, "\033[0;31m[FAILED]\033[0m"); f_log('[ERROR] Unable to create and update temporary XML file: ./tmp_rrdresize.xml'); if (!$debug_mode) { f_notify(false, '[ERROR] Unable to create and update temporary XML file: ./tmp_rrdresize.xml', false); } exit; } else { f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); } // Use RRDtool to verify if XML structure is valid f_notify('RRDtool Restore'); $file_restore = rrdtool_pipe_execute(' restore -r -f ' . $tmp_xml_file . ' ' . $tmp_rrd_file . "\r\n", $rrdtool_pipes); if (str_contains($file_restore, 'ERROR')) { f_notify(false, "\033[0;31m[FAILED]\033[0m"); // / log_missing continue; } else { f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); // create host backup directory if it is still not existing if (!is_dir($tmp_backup_folder . $host_id)) { mkdir($tmp_backup_folder . $host_id); } f_notify('RRDtool Backup'); if (!copy($file, $tmp_backup_folder . $host_id . '/' . $host_file)) { f_notify(false, "\033[0;31m[FAILED]\033[0m"); } else { f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); f_notify('RRDtool Replace'); if (!$dry_run_mode) { if (!copy($tmp_rrd_file, $file) || !chmod($file, 0644)) { f_notify(false, "\033[0;31m[FAILED]\033[0m"); } else { f_notify(false, "\033[0;32m[COMPLETED]\033[0m"); } } else { f_notify(false, "\033[0;36m[SKIPPED]\033[0m"); } } } if (!$debug_mode) { fwrite(STDOUT, '.'); } $counter++; if ($counter % 1000 == 0) { fwrite(STDOUT, (string) $counter); } } } } } if (!$debug_mode) { fwrite(STDOUT, "\r\n"); } exit; function rrdtool_parse_info(string $lines) : array { $store = []; $lines = explode(PHP_EOL, trim($lines)); foreach ($lines as $line) { [$raw_key, $raw_val] = explode(' = ', $line); $keys = preg_split('/[\.\[\]]/', $raw_key, -1, PREG_SPLIT_NO_EMPTY); $key_count = count($keys); $pointer = &$store; foreach (($keys ?: []) as $key_num => $key) { $pointer = &$pointer[$key]; if ($key_num + 1 === $key_count) { $pointer = trim($raw_val, '"'); } } } return $store; } function rrdtool_pipe_init(string $path_rrdtool) : array { $fds = [ 0 => ['pipe', 'r'], // stdin 1 => ['pipe', 'w'], // stdout 2 => ['file', '/dev/null', 'a'] // stderr ]; $process = proc_open($path_rrdtool . ' -', $fds, $pipes); // make stdin/stdout/stderr non-blocking stream_set_blocking($pipes[0], false); stream_set_blocking($pipes[1], false); return [$process, $pipes]; } function rrdtool_pipe_close(mixed $process) : void { proc_close($process); } function rrdtool_pipe_execute(string $command, mixed $pipes) : string { $stdout = ''; $return_code = fwrite($pipes[0], $command); while (!feof($pipes[1])) { $line = fgets($pipes[1], 8092); // return the complete output cause the logic behind a proxy should be as simple as possible if (substr_count((string) $line, 'OK u')) { break; } $stdout .= $line; } return $stdout; } function dirToArrayOne(string $dir) : array { global $total_files; $files = 0; $folders = 0; $result = []; $cdir = scandir($dir); if ($cdir) { foreach ($cdir as $key => $value) { if (!in_array($value, ['.', '..'], true)) { if (is_dir($dir . DIRECTORY_SEPARATOR . $value)) { $folders++; $result[$value] = dirToArrayOne($dir . DIRECTORY_SEPARATOR . $value); } else { if (substr($value, -4) == '.rrd') { $result[] = $value; $files++; $total_files++; } } } } } return $summary = ['files' => $files, 'folders' => $folders, 'content' => $result]; } function f_notify(mixed $category = false, mixed $status = false, bool $debug_mode_only = true) : bool { global $debug_mode; if (!$debug_mode && $debug_mode_only) { return true; } else { if ($category && $status) { fwrite(STDOUT, sprintf(" %-20s: %s\r\n", $category, $status)); } elseif (!$category && $status) { fwrite(STDOUT, sprintf("%s\r\n", $status)); } elseif ($category && $status === false) { fwrite(STDOUT, sprintf(' %-20s: ', $category)); } else { return false; } } return true; } function f_log(string $msg) : void { global $logging, $log_handle, $total_outdated, $total_errors, $total_skipped, $total_mismatches; fwrite($log_handle, date('Y-m-d H:i:s T', time()) . ' ' . $msg . PHP_EOL); if (str_contains($msg, '[ERROR]')) { $total_errors++; $total_skipped++; } elseif (str_contains($msg, '[OUTDATED]')) { $total_outdated++; $total_skipped++; } elseif (str_contains($msg, '[SKIPPED]')) { $total_skipped++; } } /** * display_version - displays version information * * @return void */ function display_version() : void { $version = get_cacti_cli_version(); print "Cacti RRDfile Reassign Data Template, Version $version, " . COPYRIGHT_YEARS . PHP_EOL; } /** * display_help - displays help information * * @return void */ function display_help() : void { display_version(); print PHP_EOL; print 'A simple command line utility to analyse and reassign data template settings' . PHP_EOL; print 'into RRDfiles based upon.' . PHP_EOL . PHP_EOL; print 'usage: rrdresize.php --data-template-id=[ID] --backup=[PATH] [--dry-run] [--debug, -d]' . PHP_EOL . PHP_EOL; print 'Required:' . PHP_EOL; print ' --data-template-id the numerical ID of the host' . PHP_EOL; print ' --backup path to backup original RRDfiles' . PHP_EOL . PHP_EOL; print 'Optional:' . PHP_EOL; print ' --dry-run no boost cache update and no replacement of rrdfiles' . PHP_EOL; print ' --debug, -d show detail processing data' . PHP_EOL; print ' --list-data-templates list all data templates' . PHP_EOL . PHP_EOL; }