#!/usr/bin/env php This script is only meant to run at the command line.'); } $no_http_headers = true; require(__DIR__ . '/../include/global.php'); } else { print 'FATAL: Can not initialize the Cacti API' . PHP_EOL; exit(1); } if ($config['poller_id'] > 1) { print 'FATAL: This utility is designed for the main Data Collector only' . PHP_EOL; exit(1); } // For legacy Cacti behavior if (!function_exists('cacti_sizeof')) { function cacti_sizeof($object) { return ($object === false || !is_array($object)) ? 0 : sizeof($object); } } if (!function_exists('get_cacti_cli_version')) { function get_cacti_cli_version() { return db_fetch_cell('SELECT cacti FROM version'); } } if (!function_exists('is_resource_writable')) { function is_resource_writable($path) { if ($path[strlen($path) - 1] == '/') { return is_resource_writable($path . uniqid(mt_rand()) . '.tmp'); } if (file_exists($path)) { if (($f = @fopen($path, 'a'))) { fclose($f); return true; } return false; } if (($f = @fopen($path, 'w'))) { fclose($f); unlink($path); return true; } return false; } } ini_set('max_execution_time', '0'); ini_set('display_errors', 'On'); /* setup defaults */ $debug = false; $dryrun = false; $backup = false; $overwrite = false; $user = get_current_user(); $owner = 'apache'; $ownerset = false; $oldrrd = ''; $newrrd = ''; $finrrd = ''; $time = microtime(true); global $debug; /* process calling arguments */ $parms = $_SERVER['argv']; array_shift($parms); if (cacti_sizeof($parms)) { foreach ($parms as $parameter) { if (strpos($parameter, '=')) { list($arg, $value) = explode('=', $parameter); } else { $arg = $parameter; $value = ''; } switch ($arg) { case '--oldrrd': $oldrrd = $value; if (!file_exists($oldrrd)) { print 'FATAL: File \'' . $oldrrd . '\' does not exist.' . PHP_EOL; exit(-9); } if (!is_resource_writable($oldrrd)) { print 'FATAL: File \'' . $oldrrd . '\' is not writable by this account.' . PHP_EOL; exit(-8); } break; case '--newrrd': $newrrd = $value; if (!file_exists($newrrd)) { print 'FATAL: File \'' . $newrrd . '\' does not exist.' . PHP_EOL; exit(-9); } if (!is_resource_writable($newrrd)) { print 'FATAL: File \'' . $newrrd . '\' is not writable by this account.' . PHP_EOL; exit(-8); } break; case '--finrrd': $finrrd = $value; if (!is_resource_writable(dirname($finrrd) . '/') || (file_exists($finrrd) && !is_resource_writable($finrrd))) { print 'FATAL: File \'' . $finrrd . '\' is not writable by this account.' . PHP_EOL; exit(-8); } break; case '-R': case '--owner': $owner = $value; $ownerset = true; break; case '-B': case '--backup': $backup = true; break; case '-O': case '--overwrite': $overwrite = true; break; case '-d': case '--debug': $debug = true; break; case '-D': case '--dryrun': $dryrun = true; break; case '-V': case '--version': display_version(); exit(0); case '-H': case '--help': display_help(); exit(0); default: print 'ERROR: Invalid Parameter ' . $parameter . PHP_EOL . PHP_EOL; display_help(); exit(-3); } } } /* additional error check */ if ($oldrrd == '') { print 'FATAL: You must specify a old RRDfile!' . PHP_EOL . PHP_EOL; display_help(); exit(-2); } if ($newrrd == '') { print 'FATAL: You must specify a New RRDfile!' . PHP_EOL . PHP_EOL; display_help(); exit(-2); } if ($overwrite && $finrrd == '') { $finrrd = $newrrd; } if ($finrrd == '') { print 'FATAL: You must specify a New RRDfile or use the overwrite option!' . PHP_EOL . PHP_EOL; display_help(); exit(-2); } debug('Entering Mainline'); /* let's see if we can find rrdtool */ global $rrdtool, $use_db, $db; /* see if sqlLite is available */ if (class_exists('SQLite3')) { print 'NOTE: Using SQLite Database for performance.' . PHP_EOL; $use_db = true; } else { print 'NOTE: Using Native Arrays due to lack of SQLite.' . PHP_EOL; $use_db = false; } /* verify the location of rrdtool */ $rrdtool = read_config_option('path_rrdtool'); if (!file_exists($rrdtool)) { if (substr_count(PHP_OS, 'WIN')) { $rrdtool = 'rrdtool.exe'; } else { $rrdtool = 'rrdtool'; } } $response = shell_exec($rrdtool); if (strlen($response)) { $response_array = explode(' ', $response); print 'NOTE: Using ' . $response_array[0] . ' Version ' . $response_array[1] . PHP_EOL; } else { print 'FATAL: RRDTool not found in configuration or path.' . PHP_EOL . 'Please insure RRDTool can be found using one of these methods!' . PHP_EOL; exit(-1); } /* determine the temporary file name */ $seed = mt_rand(); if (substr_count(PHP_OS, 'WIN')) { $tempdir = getenv('TEMP'); $oldxmlfile = $tempdir . '/' . str_replace('.rrd', '', basename($oldrrd)) . '.dump.' . $seed; $seed++; $newxmlfile = $tempdir . '/' . str_replace('.rrd', '', basename($newrrd)) . '.dump.' . $seed; } else { $tempdir = '/tmp'; $oldxmlfile = '/tmp/' . str_replace('.rrd', '', basename($oldrrd)) . '.dump.' . $seed; $seed++; $newxmlfile = '/tmp/' . str_replace('.rrd', '', basename($newrrd)) . '.dump.' . $seed; } if ($finrrd == '') { $finrrd = dirname($newrrd) . '/' . basename($newrrd) . '.new'; } /* execute the dump commands */ debug("Creating XML file '$oldxmlfile' from '$oldrrd'"); shell_exec("$rrdtool dump $oldrrd > $oldxmlfile"); debug("Creating XML file '$newxmlfile' from '$newrrd'"); shell_exec("$rrdtool dump $newrrd > $newxmlfile"); /* read the xml files into arrays */ if (file_exists($oldxmlfile)) { $old_output = file($oldxmlfile); /* remove the temp file */ unlink($oldxmlfile); } else { print 'FATAL: RRDtool Command Failed on \'' . $oldrrd . '\'. Please insure your RRDtool install is valid!' . PHP_EOL; exit(-12); } if (file_exists($newxmlfile)) { $new_output = file($newxmlfile); /* remove the temp file */ unlink($newxmlfile); } else { print 'FATAL: RRDtool Command Failed on \'' . $newrrd . '\'. Please insure your RRDtool install is valid!' . PHP_EOL; exit(-12); } print 'NOTE: RRDfile will be written to \'' . $finrrd . '\'' . PHP_EOL; // Read the old RRDfile into an array, flatten and remove gaps debug("Reading XML From '$oldrrd'"); $old_output = preProcessXML($old_output); $old_rrd = processXML($old_output); $old_flat = flattenXML($old_rrd); if ($use_db) { $db = createTable(); loadTable($db, $old_flat); } // Read the new RRDfile into an array debug("Reading XML From '$newrrd'"); $new_output = preProcessXML($new_output); $new_rrd = processXML($new_output); // Splice new RRDfiles array with the flattened data debug('Splicing RRDfiles'); spliceRRDs($new_rrd, $old_flat, $old_rrd['dsnames'], $db); debug('Re-Creating XML File'); $new_xml = recreateXML($new_rrd); debug('Writing XML File to Disk'); file_put_contents($newxmlfile, $new_xml); /* finally update the file XML file and Reprocess the RRDfile */ if (!$dryrun) { debug('Creating New RRDfile'); createRRDFileFromXML($newxmlfile, $finrrd); } /* remove the temp file */ unlink($newxmlfile); /* change ownership */ if ($ownerset) { if ($user == 'root') { chown($finrrd, $owner); } else { print 'ERROR: Unable to change owner. You must run as root to change owner' . PHP_EOL; } } memoryUsage(); /** spliceRRDs - This function walks through the structure of the newrrd * XML file array and for each value, if it's either '0' or 'NaN' the * script will search the flattenedXML or SQLite table for the closest * match and save that into the final array, that will then be written * back out to an XML file and re-loaded into an RRDfile. * @param mixed $new_rrd * @param mixed $old_flat * @param mixed $old_dsnames */ function spliceRRDs(&$new_rrd, &$old_flat, &$old_dsnames) { if (cacti_sizeof($new_rrd) && cacti_sizeof($old_flat)) { if (isset($new_rrd['rra'])) { foreach ($new_rrd['rra'] as $rra_num => $rra) { $cf = $new_rrd['rra'][$rra_num]['cf']; $pdp = $new_rrd['rra'][$rra_num]['pdp_per_row']; if (isset($rra['database'])) { foreach ($rra['database'] as $cdp_ds_num => $value) { $dsname = $new_rrd['ds'][$cdp_ds_num]['name']; $olddsnum = $old_dsnames[$dsname]; $last_good = 'NaN'; debug("Splicing DSName $dsname NewId $cdp_ds_num OldId $olddsnum"); foreach ($value as $time => $v) { if ($v == 'NaN' || $v == 0) { if ($time < $old_flat['mintime']) { continue; } $old_value = getOldRRDValue($old_flat, $olddsnum, $cf, $time); if ($old_value != 'NaN') { $last_good = $old_value; $new_rrd['rra'][$rra_num]['database'][$cdp_ds_num][$time] = $old_value; } elseif ($last_good != 'NaN') { $new_rrd['rra'][$rra_num]['database'][$cdp_ds_num][$time] = $last_good; } } else { $last_good = $v; } $new_value = $new_rrd['rra'][$rra_num]['database'][$cdp_ds_num][$time]; //print "DSName: $dsname, NewVAlue: $new_value, OrigValue: $v, OldValue: $old_value\n"; } } } else { print 'FATAL: RRA database is Invalid' . PHP_EOL; } } } else { print 'FATAL: One of RRA\'s is Invalid' . PHP_EOL; } } else { print 'FATAL: One of RRD\'s is Invalid' . PHP_EOL; } } /** getOldRRDValue - scan the flattened array for a good timestamp * and return the nearest value for that timestamp. * * The flattened array is sorted by timestamp in reverse order. * If the SQLite table is available, this function will prefer * that table over traversing the array. * @param mixed $old_flat * @param mixed $dsnum * @param mixed $cf * @param mixed $time */ function getOldRRDValue(&$old_flat, $dsnum, $cf, $time) { global $use_db, $db; if ($use_db) { $stmt = $db->prepare("SELECT * FROM dsData WHERE dsid = $dsnum AND cf = '$cf' AND timestamp <= $time ORDER BY timestamp DESC LIMIT 1"); $result = $stmt->execute(); while ($row = $result->fetchArray()) { return $row['value']; } return 'NaN'; } else { if (!isset($old_flat[$dsnum][$cf])) { debug("CF $cf Not found in flattened data."); return 'NaN'; } else { $first = true; foreach ($old_flat[$dsnum][$cf] as $timestamp => $data) { if ($first && $time > $timestamp) { // The time is before any good data in the RRDfile debug("No Good data found. Timestamp $time newer than the newest timestamp $timestamp"); return 'NaN'; } if ($time >= $timestamp) { debug("Good for $time offset is " . number_format(abs($time - $timestamp), 0)); return $data; } $first = false; } debug("No Good data found. Timestamp $time"); return 'NaN'; } } } /** recreateXML - Take the data from the modified XML and re-create the XML file that * will then be turned back into an RRDfile. * * The array structure is documented below. * * $rrd['version']; * $rrd['step']; * $rrd['lastupdate']; * $rrd['ds'][$ds_num]['name']; * $rrd['ds'][$ds_num]['type']; * $rrd['ds'][$ds_num]['minimal_heartbeat']; * $rrd['ds'][$ds_num]['min']; * $rrd['ds'][$ds_num]['max']; * $rrd['ds'][$ds_num]['last_ds']; * $rrd['ds'][$ds_num]['value']; * $rrd['ds'][$ds_num]['unknown_sec']; * $rrd['rra'][$rra_num]['cf']; * $rrd['rra'][$rra_num]['pdp_per_row']; * $rrd['rra'][$rra_num]['params']['xff']; * $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['primary_value']; * $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['secondary_value']; * $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['value']; * $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['unknown_datapoints']; * $rrd['rra'][$rra_num]['database'][$cdp_ds_num]['time']; * @param mixed $new_rrd */ function recreateXML($new_rrd) { $rrd = "\n"; $rrd .= "\t " . $new_rrd['version'] . " \n"; $rrd .= "\t " . $new_rrd['step'] . " \n"; $rrd .= "\t " . $new_rrd['lastupdate'] . " \n"; foreach ($new_rrd['ds'] as $dsnum => $ds) { $rrd .= "\t\n"; $rrd .= "\t\t " . $ds['name'] . " \n"; $rrd .= "\t\t " . $ds['type'] . " \n"; $rrd .= "\t\t " . $ds['minimal_heartbeat'] . " \n"; $rrd .= "\t\t " . $ds['min'] . " \n"; $rrd .= "\t\t " . $ds['max'] . " \n"; $rrd .= "\t\t " . $ds['last_ds'] . " \n"; $rrd .= "\t\t " . $ds['value'] . " \n"; $rrd .= "\t\t " . $ds['unknown_sec'] . " \n"; $rrd .= "\t\n"; } foreach ($new_rrd['rra'] as $rra_num => $rra) { $rrd .= "\t\n"; $rrd .= "\t\t " . $rra['cf'] . " \n"; $rrd .= "\t\t " . $rra['pdp_per_row'] . " \n"; $rrd .= "\t\t\n"; $rrd .= "\t\t\t " . $rra['params']['xff'] . " \n"; $rrd .= "\t\t\n"; $rrd .= "\t\t\n"; foreach ($new_rrd['rra'][$rra_num]['cdp_prep'] as $cdp_ds_num => $pdp) { $rrd .= "\t\t\t\n"; $rrd .= "\t\t\t\t " . $pdp['primary_value'] . " \n"; $rrd .= "\t\t\t\t " . $pdp['secondary_value'] . " \n"; $rrd .= "\t\t\t\t " . $pdp['value'] . " \n"; $rrd .= "\t\t\t\t " . $pdp['unknown_datapoints'] . " \n"; $rrd .= "\t\t\t\n"; $output = array(); foreach ($new_rrd['rra'][$rra_num]['database'] as $dsnum => $v) { foreach ($v as $time => $value) { $output[$time][$dsnum] = $value; } } } $rrd .= "\t\t\n"; $rrd .= "\t\t\n"; foreach ($output as $time => $v) { $rrd .= "\t\t\t"; foreach ($v as $dsnum => $value) { $rrd .= ' ' . $value . ' '; } $rrd .= "\n"; } $rrd .= "\t\t\n"; $rrd .= "\t\n"; } $rrd .= ''; return $rrd; } /* memoryUsage - Report the peak memory usage of the php script */ function memoryUsage() { global $time; $mem_usage = memory_get_usage(true); if ($mem_usage < 1024) { $memstr = $mem_usage . ' B'; } elseif ($mem_usage < 1048576) { $memstr = round($mem_usage / 1024, 2) . ' KB'; } else { $memstr = round($mem_usage / 1048576, 2) . ' MB'; } print 'NOTE: Time:' . round(microtime(true) - $time, 2) . ', RUsage:' . $memstr . PHP_EOL; } /** flattenXML - Take all the data from the various data sources and * by Consolidation Function, sort the values by timestamp so that * the new RRDfile can pull values that make sense to fill in the * time where there may be no data. * * Additionally, remove any NaN values and replace with the last * good known value to fill gaps in the graphs. * * The form of the output array will be as follows: * * $newxml[$datasourceid][$cf][$timestamp] = value * $newxml['mintime'] = value * * The data will only go back as far as the source RRDfile. * * @param mixed $xml */ function flattenXML(&$xml) { global $debug; $newxml = array(); $maxarray = array(); $mintime = 'NaN'; if (cacti_sizeof($xml['rra'])) { foreach ($xml['rra'] as $rraid => $data) { $cf = $data['cf']; foreach ($data['database'] as $dsid => $timedata) { $i = 0; // Don't load data from less granular RRA's if (isset($maxarray[$dsid][$cf])) { $maxtoload = $maxarray[$dsid][$cf]; } else { $maxtoload = 9999999999999; } foreach ($timedata as $timestamp => $value) { if ($i == 0 && $value != 'NaN') { $maxarray[$dsid][$cf] = $timestamp; $i++; } if ($timestamp <= $maxtoload) { $newxml[$dsid][$cf][$timestamp] = $value; if ($mintime == 'NaN') { $mintime = $timestamp; } elseif ($timestamp < $mintime) { $mintime = $timestamp; } } } } } foreach ($newxml as $dsid => $data) { foreach ($data as $cf => $timedata) { // Sort the data ksort($timedata); // Get rid of NaN $last_data = 0; foreach ($timedata as $timestamp => $data) { if ($data == 'NaN') { $timedata[$timestamp] = $last_data; } else { $last_data = $data; } } krsort($timedata); $newxml[$dsid][$cf] = $timedata; if ($debug) { $stats = sprintf( 'DS:%2d, CF:%8s, Vals:%10s, Max:%10s, Avg:%10s', $dsid, $cf, number_format(cacti_sizeof($timedata)), number_format(getMaxValue($timedata), 4), number_format(getAvgValue($timedata), 4) ); debug($stats); } } } } $newxml['mintime'] = $mintime; return $newxml; } /** getMaxValue - Obtains tha max value from the timestamp array * for use in debug output. * @param mixed $data */ function getMaxValue(&$data) { $max = 0; foreach ($data as $timestamp => $value) { if ($value != 'NaN' && $value > $max) { $max = $value; } } return $max; } /** getAvgValue - Obtains tha average value from the timestamp array * for use in debug output. * @param mixed $data */ function getAvgValue(&$data) { $entries = cacti_sizeof($data); $total = array_sum($data); if ($entries) { return $total / $entries; } else { return 0; } } /** processXML - Read all the XML into an array. The format of the array * will be as show below. This way it can be processed reverted back * to array format at the end of the merge process. * * $rrd['version']; * $rrd['step']; * $rrd['lastupdate']; * $rrd['ds'][$ds_num]['name']; * $rrd['ds'][$ds_num]['type']; * $rrd['ds'][$ds_num]['minimal_heartbeat']; * $rrd['ds'][$ds_num]['min']; * $rrd['ds'][$ds_num]['max']; * $rrd['ds'][$ds_num]['last_ds']; * $rrd['ds'][$ds_num]['value']; * $rrd['ds'][$ds_num]['unknown_sec']; * $rrd['rra'][$rra_num]['cf']; * $rrd['rra'][$rra_num]['pdp_per_row']; * $rrd['rra'][$rra_num]['params']['xff']; * $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['primary_value']; * $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['secondary_value']; * $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['value']; * $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['unknown_datapoints']; * $rrd['rra'][$rra_num]['database'][$cdp_ds_num]['time']; * @param mixed $output */ function processXML(&$output) { $rrd = array(); $dsnames = array(); $rra_num = 0; $ds_num = 0; $cdp_ds_num = 0; $in_ds = false; $in_rra = false; $in_db = false; $in_parm = false; $in_cdp = false; $in_cdp_ds = false; if (cacti_sizeof($output)) { foreach ($output as $line) { if (substr_count($line, '')) { $line = trim(str_replace('', '', str_replace('', '', $line))); $larray = explode('', $line); $time = trim(str_replace('', '', str_replace('', '', $larray[0]))); array_shift($larray); $tdsno = 0; foreach ($larray as $l) { $value = trim(str_replace('', '', $l)); $rrd['rra'][$rra_num]['database'][$tdsno][$time] = $value; $tdsno++; } } elseif (substr_count($line, '')) { $rrd['lastupdate'] = XMLrip('lastupdate', $line); } elseif (substr_count($line, '')) { $line = trim(str_replace('', '', $line)); $rrd['version'] = XMLrip('version', $line); } elseif (substr_count($line, '')) { $rrd['step'] = XMLrip('step', $line); } elseif (substr_count($line, '')) { $in_rra = true; } elseif (substr_count($line, '')) { $in_rra = false; $cdp_ds_num = 0; $rra_num++; } elseif (substr_count($line, '')) { if (!$in_cdp) { $in_ds = true; } } elseif (substr_count($line, '')) { if ($in_ds) { $in_ds = false; $ds_num++; } else { $in_cdp_ds = false; $cdp_ds_num++; } } elseif (substr_count($line, '')) { $in_cdp = true; } elseif (substr_count($line, '')) { $in_cdp = false; } elseif (substr_count($line, '')) { $in_parm = true; } elseif (substr_count($line, '')) { $in_parm = false; } elseif (substr_count($line, '')) { $rrd['ds'][$ds_num]['name'] = XMLrip('name', $line); $dsnames[] = XMLrip('name', $line); } elseif (substr_count($line, '')) { $rrd['ds'][$ds_num]['type'] = XMLrip('type', $line); } elseif (substr_count($line, '')) { $rrd['ds'][$ds_num]['minimal_heartbeat'] = XMLrip('minimal_heartbeat', $line); } elseif (substr_count($line, '')) { $rrd['ds'][$ds_num]['max'] = XMLrip('max', $line); } elseif (substr_count($line, '')) { $rrd['ds'][$ds_num]['min'] = XMLrip('min', $line); } elseif (substr_count($line, '')) { $rrd['ds'][$ds_num]['last_ds'] = XMLrip('last_ds', $line); } elseif (substr_count($line, '')) { if ($in_rra) { $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['value'] = XMLrip('value', $line); } else { $rrd['ds'][$ds_num]['value'] = XMLrip('value', $line); } } elseif (substr_count($line, '')) { $rrd['ds'][$ds_num]['unknown_sec'] = XMLrip('unknown_sec', $line); } elseif (substr_count($line, '')) { $rrd['rra'][$rra_num]['cf'] = XMLrip('cf', $line); } elseif (substr_count($line, '')) { $rrd['rra'][$rra_num]['pdp_per_row'] = XMLrip('pdp_per_row', $line); } elseif (substr_count($line, '')) { $rrd['rra'][$rra_num]['params']['xff'] = XMLrip('xff', $line); } elseif (substr_count($line, '')) { $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['primary_value'] = XMLrip('primary_value', $line); } elseif (substr_count($line, '')) { $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['secondary_value'] = XMLrip('secondary_value', $line); } elseif (substr_count($line, '')) { $rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['unknown_datapoints'] = XMLrip('unknown_datapoints', $line); } } } if (cacti_sizeof($dsnames)) { foreach ($dsnames as $index => $name) { $rrd['dsnames'][$name] = $index; } } return $rrd; } /* All Functions */ function createRRDFileFromXML($xmlfile, $rrdfile) { global $rrdtool; /* execute the dump command */ print 'NOTE: Re-Importing \'' . $xmlfile . '\' to \'' . $rrdfile . '\'' . PHP_EOL; $response = shell_exec("$rrdtool restore -f -r $xmlfile $rrdfile"); if (strlen($response)) { print $response . PHP_EOL; } } function XMLrip($tag, $line) { return trim(str_replace("<$tag>", '', str_replace("", '', $line))); } function writeXMLFile($output, $xmlfile) { return file_put_contents($xmlfile, $output); } function backupRRDFile($rrdfile) { global $tempdir, $seed, $html; $backupdir = $tempdir; if (file_exists($backupdir . '/' . basename($rrdfile))) { $newfile = basename($rrdfile) . '.' . $seed; } else { $newfile = basename($rrdfile); } print 'NOTE: Backing Up \'' . $rrdfile . '\' to \'' . $backupdir . '/' . $newfile . '\'' . PHP_EOL; return copy($rrdfile, $backupdir . '/' . $newfile); } /** preProcessXML - This function strips the timestamps off the XML dump * and loads that data into an array along with the remainder of the * XML data for future processing. * @param mixed $output */ function preProcessXML(&$output) { if (cacti_sizeof($output)) { foreach ($output as $line) { $line = trim($line); $date = ''; if ($line == '') { continue; } else { /* is there a comment, remove it */ $comment_start = strpos($line, ''); $row = strpos($line, ''); if ($row > 0) { $date = trim(substr($line,strpos($line,'/')+1,11)); } if ($comment_start == 0) { $line = trim(substr($line, $comment_end + 3)); } else { $line = trim(substr($line, 0, $comment_start - 1) . substr($line, $comment_end + 3)); } if (!empty($date)) { $line = str_replace('', " $date ", $line); } } if ($line != '') { $new_array[] = $line; } } } /* transfer the new array back to the original array */ return $new_array; } } function debug($string) { global $debug; if ($debug) { print 'DEBUG: ' . trim($string) . PHP_EOL; } } /** createTable - This function creates a SQLite memory table * to hold the flattened XML file in for replay. */ function createTable() { /* table in memory */ $db = new SQLite3(':memory:'); /* create the table */ $db->exec('CREATE TABLE dsData ( dsid int, cf char(10) NOT NULL, timestamp int, value real NOT NULL, PRIMARY KEY (dsid, cf, timestamp))'); $db->exec('CREATE INDEX dsid_cf_timestamp ON dsData (dsid, cf, timestamp)'); $db->exec('CREATE INDEX timestamp ON dsData (timestamp)'); return $db; } /** loadTable - This function loads the flattened XML file into * the SQLite database for replaying the RRDfile dump data * into the new XML file. * @param mixed $db * @param mixed $records */ function loadTable($db, &$records) { $db->exec('BEGIN TRANSACTION'); $sql = ''; foreach ($records as $dsid => $cfdata) { if (is_numeric($dsid)) { foreach ($cfdata as $cf => $timedata) { $i = 0; foreach ($timedata as $timestamp => $value) { $sql .= ($sql != '' ? ', ' : '') . '(' . $dsid . ',"' . $cf . '",' . $timestamp . ', ' . $value . ')'; $i++; if ($i > 50) { if ($sql != '') { $db->exec("INSERT INTO dsData (dsid, cf, timestamp, value) VALUES $sql"); } $sql = ''; $i = 0; } } if ($sql != '') { $db->exec("INSERT INTO dsData (dsid, cf, timestamp, value) VALUES $sql"); } $sql = ''; } } } $db->exec('COMMIT TRANSACTION'); } function display_version() { if (!defined('COPYRIGHT_YEARS')) { define('COPYRIGHT_YEARS', '2004-2023'); } $version = get_cacti_cli_version(); print 'Cacti RRDfile Splicer Utility, Version ' . $version . ', ' . COPYRIGHT_YEARS . PHP_EOL; } /** display_help - Displays usage information about how to utilize * this program. */ function display_help() { display_version(); print PHP_EOL . 'usage: splice_rrd.php --oldrrd=file --newrrd=file [--finrrd=file]' . PHP_EOL; print ' [--owner=apache] [--debug] [--dryrun]' . PHP_EOL . PHP_EOL; print 'The splice_rrd.php file is designed to allow two RRDfiles to be merged.' . PHP_EOL . PHP_EOL; print 'This utility can effectively change the resolution/step of an RRDfile' . PHP_EOL; print 'so long as the new RRDfile already has the correct step.' . PHP_EOL . PHP_EOL; print 'The Old and New input parameters are mandatory. If the finrrd option is' . PHP_EOL; print 'not specified, it will be the newrrd plus a timestamp.' . PHP_EOL . PHP_EOL; print '--oldrrd=file - The old RRDfile that contains old data.' . PHP_EOL; print '--newrrd=file - The new RRDfile that contains more recent data.' . PHP_EOL; print '--finrrd=file - The final RRDfile that contains data from both rrdfiles.' . PHP_EOL . PHP_EOL; print '--owner=apache - Change the owner of the resulting file. Note requires root.' . PHP_EOL; print '--dryrun - Simply test the splicing of the RRDfiles, don\'t write.' . PHP_EOL . PHP_EOL; }