mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
* PHPStan Level 8 - Phase 1 Conversion
Reduces the real (no-baseline) PHPStan level 8 error count from ~2,724 to 425
and regenerates phpstan-baseline.neon accordingly (8,941 -> 1,921 lines).
Core type-safety changes
- lib/database.php: narrow db_fetch_row()/db_fetch_row_prepared() to return
array (was bool|array) and db_fetch_assoc()/db_fetch_assoc_prepared() to
return array (was mixed); the _prepared workers coerce the connection-failure
path to an empty array so the declared type is honest. empty()/cacti_sizeof()
checks are unaffected.
- lib/database.php: narrow $db_conn guards from is_object() to instanceof PDO in
the transaction/quote/insert-id helpers so the PDO calls resolve.
- lib/mib_cache.php: the fluent MibCache methods mib()/object()/table()/row()
now return self instead of a bare object so chained calls type-check.
Type-safety casts and guards (mechanical, behaviour-preserving)
- Cast string|false/|null builtin results feeding string parameters
(json_decode, base64_encode, trim, htmle, preg_replace, explode, etc.).
- Cast int|string / int|false values feeding int id/pid parameters.
- Guard resource|false fopen()/popen() handles before fwrite/fclose/fread, and
wrap foreach over array|false/list|false results with `?: []`.
Genuine bugs found and fixed
- cli/add_tree.php / lib/api_tree.php: site tree nodes could not be created via
the CLI - the --site-id option was never parsed, $nodeTypes had no 'site'
entry (passing null to api_tree_item_save(int $type) -> TypeError), and
api_tree_item_save() never persisted site_id. All three fixed.
- tree.php: leaves_exist() is typed : int but returned db_fetch_assoc_prepared()
(an array of rows) for a SELECT COUNT(*); now uses db_fetch_cell_prepared()
and returns the scalar count.
- lib/mib_cache.php: $entries_per_object was a float (division / ceil) used as
an array key (PHP 8.1 implicit float-to-int key deprecation); now cast to int.
Config
- .phpstan.neon: add notIdentical.alwaysTrue and booleanAnd.alwaysFalse to the
existing always-true/false ignore family (same "PHPStan gets confused"
dynamic-constant cause already covered by identical.alwaysTrue,
notEqual.alwaysTrue, booleanAnd.leftAlwaysTrue, etc.).
- phpstan-baseline.neon: regenerated; the remaining 425 entries are the
untouched long tail for Phase 2.
* Remove redundant @phpstan-ignore comments
Follow-up cleanup for the level 8 Phase 1 work. Removes 14 inline
@phpstan-ignore comments that no longer suppress any reported error:
- cli/repair_database.php: `@phpstan-ignore booleanAnd.alwaysFalse` is now
covered by the global booleanAnd.alwaysFalse ignore added to .phpstan.neon.
- 13 others (host.php, include/global.php, include/session.php,
lib/api_automation.php x2, lib/functions.php x2, lib/html_utility.php x2,
lib/installer.php, lib/package.php, lib/reports.php, lib/rrd.php) no longer
match any reported error after the type-safety fixes (e.g. the
db_fetch_row_prepared() return-type narrowing removed the offset-assign
errors the api_automation ignores were suppressing).
Verified with reportUnmatchedIgnoredErrors enabled: 0 unmatched ignores
remain and the full level 8 run still reports 0 errors.
* ci: satisfy validator ratchet and refresh security sink baselines
- lib/api_tree.php: give the new tree-item site_id a real numeric
constraint ('^[-0-9]+$', matching html_reports.php) instead of an
empty regex, so the validator constraint ratchet passes without
bumping its baseline.
- tests/security/baselines: refresh the three sink/hotspot signatures
whose code text changed from the (string) casts added in this branch
(lib/poller.php setsid, cli/rrdresize.php json_encode, lib/import.php
simplexml_load_string).
* ci(js): allow DOMPurify 3.4.16 and update sync_js pin test
The dependabot bump (#8134) moved package.json/package-lock.json to
DOMPurify 3.4.16 on develop but left tests/js/sync_js.test.mjs pinned to
3.4.15, so the PR merge commit fails the 'DOMPurify is pinned' contract.
Bring package.json/package-lock.json to 3.4.16 (matching develop) and
update the pin test to expect 3.4.16.
* test: update sql.php null-handling assertion for the (string) cast
This branch changed scripts/sql.php from trim(\ ?? '') to
trim((string) \) (the phpstan-safe form that coerces both null and
false from shell_exec()). Update SqlScriptsTest's null-return assertion
to match the new cast instead of the removed '?? ''.
* fix: address Copilot review - ownership IDs, fopen guards, mib_cache bound
- chown()/chgrp(): pass integer UID/GID from fileowner()/filegroup()
(guarding the false lookup) instead of string-casting them, which made
them resolve account/group names like '1001' and fail. Covers
lib/api_device.php, lib/boost.php and poller_maintenance.php (x2).
- fopen() failure now short-circuits instead of reporting success:
lib/package.php returns false, lib/clog_webapi.php and utilities.php
render an error, and snmpagent_mibcachechild.php skips the cache write
and sentinel unlink so a concurrent writer's lock is preserved.
- lib/mib_cache.php: keep the float quotient as the loop bound and cast
only the array offset, so a non-even entries/columns split no longer
truncates the loop and drops a cached row.
* revert: keep db_fetch_* returning false on error; baseline Level 8 instead
Per review, restoring the intentional fail-safe: db_fetch_row[_prepared]()
return bool|array and db_fetch_assoc[_prepared]() return mixed again, so a
genuine SQL error (not just a missing connection) is signalled as false and
the === false checks in cdef_is_in_use(), boost_process_* and aggregate.php
work as before. Dropped the is_array() ? : [] coercion.
Level 8 stays green by regenerating phpstan-baseline.neon to capture the
re-introduced bool|array/mixed call-site errors (the ~1,850 the narrowing
had eliminated). Tightening those callers to drop them from the baseline is
Phase 2 work.
---------
Co-authored-by: TheWitness <copilot@users.noreply.github.com>
1147 lines
31 KiB
PHP
Executable File
1147 lines
31 KiB
PHP
Executable File
#!/usr/bin/env php
|
|
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| of the License, or (at your option) any later version. |
|
|
| |
|
|
| This program is distributed in the hope that it will be useful, |
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
| GNU General Public License for more details. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
| This code is designed, written, and maintained by the Cacti Group. See |
|
|
| about.php and/or the AUTHORS file for specific developer information. |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
// ------------------------------------------------------------
|
|
// If not running from Cacti, make sure you set the RRDtool
|
|
// path on this line.
|
|
// ------------------------------------------------------------
|
|
if (file_exists('/usr/bin/rrdtool')) {
|
|
$rrdtool = '/usr/bin/rrdtool';
|
|
} elseif (file_exists('/usr/local/bin/rrdtool')) {
|
|
$rrdtool = '/usr/local/bin/rrdtool';
|
|
}
|
|
|
|
if (file_exists(__DIR__ . '/../include/cli_check.php')) {
|
|
require(__DIR__ . '/../include/cli_check.php');
|
|
$from_cacti = true;
|
|
} else {
|
|
$from_cacti = false;
|
|
}
|
|
|
|
if ($from_cacti) {
|
|
if (POLLER_ID > 1) {
|
|
print 'FATAL: This utility is designed for the main Data Collector only' . PHP_EOL;
|
|
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('cacti_sizeof')) {
|
|
function cacti_sizeof(mixed $array) : int {
|
|
return ($array === false || !is_array($array)) ? 0 : sizeof($array);
|
|
}
|
|
|
|
function cacti_count(mixed $array) : int {
|
|
return ($array === false || !is_array($array)) ? 0 : count($array);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_cacti_cli_version')) {
|
|
function get_cacti_cli_version() : string {
|
|
return (string) db_fetch_cell('SELECT cacti FROM version');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('is_resource_writable')) {
|
|
function is_resource_writable(string $path) : bool {
|
|
if ($path[strlen($path) - 1] == '/') {
|
|
return is_resource_writable($path . uniqid('', true) . '.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 (str_contains($parameter, '=')) {
|
|
[$arg, $value] = explode('=', $parameter, 2);
|
|
} 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($from_cacti) {
|
|
print 'NOTE: Running from the Cacti Server' . PHP_EOL;
|
|
} else {
|
|
print 'NOTE: Not running from the Cacti Server' . PHP_EOL;
|
|
}
|
|
|
|
// 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 SQLite 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
|
|
if (function_exists('read_config_option')) {
|
|
$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(cacti_escapeshellcmd($rrdtool));
|
|
|
|
if (strlen((string) $response)) {
|
|
$response_array = explode(' ', (string) $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);
|
|
}
|
|
|
|
/* The dump files and the backups were previously named from the RRD basename
|
|
* and mt_rand() directly in a world writable directory, and were created by
|
|
* shell redirection and copy(), both of which follow symlinks. A local user
|
|
* who guessed a name could have the output written over a file of their
|
|
* choosing. Everything now goes in one private directory created for this run,
|
|
* so the names cannot be claimed in advance. */
|
|
$tempdir = tempnam(sys_get_temp_dir(), 'cacti_splice_');
|
|
|
|
if ($tempdir === false || !unlink($tempdir) || !mkdir($tempdir, 0700)) {
|
|
print 'FATAL: Unable to create a private working directory' . PHP_EOL;
|
|
exit(1);
|
|
}
|
|
|
|
$oldxmlfile = $tempdir . '/' . str_replace('.rrd', '', basename($oldrrd)) . '.dump';
|
|
$newxmlfile = $tempdir . '/' . str_replace('.rrd', '', basename($newrrd)) . '.dump';
|
|
|
|
if ($finrrd === '') {
|
|
$finrrd = dirname($newrrd) . '/' . basename($newrrd) . '.new';
|
|
}
|
|
|
|
// execute the dump commands
|
|
debug("Creating XML file '$oldxmlfile' from '$oldrrd'");
|
|
shell_exec(cacti_escapeshellcmd($rrdtool) . ' dump ' . cacti_escapeshellarg($oldrrd) . ' > ' . cacti_escapeshellarg($oldxmlfile));
|
|
|
|
debug("Creating XML file '$newxmlfile' from '$newrrd'");
|
|
shell_exec(cacti_escapeshellcmd($rrdtool) . ' dump ' . cacti_escapeshellarg($newrrd) . ' > ' . cacti_escapeshellarg($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']);
|
|
|
|
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 array $new_rrd
|
|
* @param array $old_flat
|
|
* @param array $old_dsnames
|
|
*
|
|
* @return void
|
|
*/
|
|
function spliceRRDs(array &$new_rrd, array &$old_flat, array &$old_dsnames) : void {
|
|
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 array $old_flat
|
|
* @param int $dsnum
|
|
* @param string $cf
|
|
* @param int $time
|
|
*
|
|
* @return string - The value from the old RRDfile
|
|
*/
|
|
function getOldRRDValue(array &$old_flat, int $dsnum, string $cf, int $time) : string {
|
|
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 array $new_rrd - The Array structure of the new RRDfile
|
|
*
|
|
* @return string - The new RRDfile output
|
|
*/
|
|
function recreateXML(array $new_rrd) : string {
|
|
$rrd = "<rrd>\n";
|
|
$rrd .= "\t<version> " . $new_rrd['version'] . " </version>\n";
|
|
$rrd .= "\t<step> " . $new_rrd['step'] . " </step>\n";
|
|
$rrd .= "\t<lastupdate> " . $new_rrd['lastupdate'] . " </lastupdate>\n";
|
|
|
|
foreach ($new_rrd['ds'] as $dsnum => $ds) {
|
|
$rrd .= "\t<ds>\n";
|
|
$rrd .= "\t\t<name> " . $ds['name'] . " </name>\n";
|
|
$rrd .= "\t\t<type> " . $ds['type'] . " </type>\n";
|
|
$rrd .= "\t\t<minimal_heartbeat> " . $ds['minimal_heartbeat'] . " </minimal_heartbeat>\n";
|
|
$rrd .= "\t\t<min> " . $ds['min'] . " </min>\n";
|
|
$rrd .= "\t\t<max> " . $ds['max'] . " </max>\n";
|
|
$rrd .= "\t\t<last_ds> " . $ds['last_ds'] . " </last_ds>\n";
|
|
$rrd .= "\t\t<value> " . $ds['value'] . " </value>\n";
|
|
$rrd .= "\t\t<unknown_sec> " . $ds['unknown_sec'] . " </unknown_sec>\n";
|
|
$rrd .= "\t</ds>\n";
|
|
}
|
|
|
|
foreach ($new_rrd['rra'] as $rra_num => $rra) {
|
|
$output = [];
|
|
|
|
$rrd .= "\t<rra>\n";
|
|
$rrd .= "\t\t<cf> " . $rra['cf'] . " </cf>\n";
|
|
$rrd .= "\t\t<pdp_per_row> " . $rra['pdp_per_row'] . " </pdp_per_row>\n";
|
|
$rrd .= "\t\t<params>\n";
|
|
$rrd .= "\t\t\t<xff> " . $rra['params']['xff'] . " </xff>\n";
|
|
$rrd .= "\t\t</params>\n";
|
|
$rrd .= "\t\t<cdp_prep>\n";
|
|
|
|
foreach ($new_rrd['rra'][$rra_num]['cdp_prep'] as $cdp_ds_num => $pdp) {
|
|
$rrd .= "\t\t\t<ds>\n";
|
|
$rrd .= "\t\t\t\t<primary_value> " . $pdp['primary_value'] . " </primary_value>\n";
|
|
$rrd .= "\t\t\t\t<secondary_value> " . $pdp['secondary_value'] . " </secondary_value>\n";
|
|
$rrd .= "\t\t\t\t<value> " . $pdp['value'] . " </value>\n";
|
|
$rrd .= "\t\t\t\t<unknown_datapoints> " . $pdp['unknown_datapoints'] . " </unknown_datapoints>\n";
|
|
$rrd .= "\t\t\t</ds>\n";
|
|
|
|
foreach ($new_rrd['rra'][$rra_num]['database'] as $dsnum => $v) {
|
|
foreach ($v as $time => $value) {
|
|
$output[$time][$dsnum] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
$rrd .= "\t\t</cdp_prep>\n";
|
|
$rrd .= "\t\t<database>\n";
|
|
|
|
foreach ($output as $time => $v) {
|
|
$rrd .= "\t\t\t<row>";
|
|
|
|
foreach ($v as $dsnum => $value) {
|
|
$rrd .= '<v> ' . $value . ' </v>';
|
|
}
|
|
$rrd .= "</row>\n";
|
|
}
|
|
|
|
$rrd .= "\t\t</database>\n";
|
|
$rrd .= "\t</rra>\n";
|
|
}
|
|
|
|
$rrd .= '</rrd>';
|
|
|
|
return $rrd;
|
|
}
|
|
|
|
/**
|
|
* memoryUsage - Report the peak memory usage of the php script
|
|
*
|
|
* @return void
|
|
*/
|
|
function memoryUsage() : void {
|
|
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 array $xml - The xml data to be flattened
|
|
*
|
|
* @return array - The flattened XML file
|
|
*/
|
|
function flattenXML(array &$xml) : array {
|
|
global $debug;
|
|
|
|
$newxml = [];
|
|
$maxarray = [];
|
|
$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 the max value from the timestamp array
|
|
* for use in debug output.
|
|
*
|
|
* @param array $data - An array of objects to get the max from
|
|
*
|
|
* @return float - The maximum value
|
|
*/
|
|
function getMaxValue(array &$data) : float {
|
|
$max = 0;
|
|
|
|
foreach ($data as $timestamp => $value) {
|
|
if ($value != 'NaN' && $value > $max) {
|
|
$max = $value;
|
|
}
|
|
}
|
|
|
|
return $max;
|
|
}
|
|
|
|
/**
|
|
* getAvgValue - Obtains the average value from the timestamp array
|
|
* for use in debug output.
|
|
*
|
|
* @param array $data - An array of objects to get the average from
|
|
*
|
|
* @return float - The average value
|
|
*/
|
|
function getAvgValue(mixed &$data) : float {
|
|
$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 array $output
|
|
*
|
|
* @return array - The processed XML data
|
|
*/
|
|
function processXML(array &$output) : array {
|
|
$rrd = [];
|
|
$dsnames = [];
|
|
$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, '<row>')) {
|
|
$line = trim(str_replace('<row>', '', str_replace('</row>', '', (string) $line)));
|
|
$larray = explode('<v>', $line);
|
|
$time = trim(str_replace('<date>', '', str_replace('</date>', '', $larray[0])));
|
|
|
|
array_shift($larray);
|
|
$tdsno = 0;
|
|
|
|
foreach ($larray as $l) {
|
|
$value = trim(str_replace('</v>', '', $l));
|
|
$rrd['rra'][$rra_num]['database'][$tdsno][$time] = $value;
|
|
$tdsno++;
|
|
}
|
|
} elseif (substr_count($line, '<lastupdate>')) {
|
|
$rrd['lastupdate'] = XMLrip('lastupdate', $line);
|
|
} elseif (substr_count($line, '<version>')) {
|
|
$line = trim(str_replace('<rrd>', '', $line));
|
|
|
|
$rrd['version'] = XMLrip('version', $line);
|
|
} elseif (substr_count($line, '<step>')) {
|
|
$rrd['step'] = XMLrip('step', $line);
|
|
} elseif (substr_count($line, '<rra>')) {
|
|
$in_rra = true;
|
|
} elseif (substr_count($line, '</rra>')) {
|
|
$in_rra = false;
|
|
|
|
$cdp_ds_num = 0;
|
|
|
|
$rra_num++;
|
|
} elseif (substr_count($line, '<ds>')) {
|
|
if (!$in_cdp) {
|
|
$in_ds = true;
|
|
}
|
|
} elseif (substr_count($line, '</ds>')) {
|
|
if ($in_ds) {
|
|
$in_ds = false;
|
|
$ds_num++;
|
|
} else {
|
|
$in_cdp_ds = false;
|
|
$cdp_ds_num++;
|
|
}
|
|
} elseif (substr_count($line, '<cdp_prep>')) {
|
|
$in_cdp = true;
|
|
} elseif (substr_count($line, '</cdp_prep>')) {
|
|
$in_cdp = false;
|
|
} elseif (substr_count($line, '<params>')) {
|
|
$in_parm = true;
|
|
} elseif (substr_count($line, '</params>')) {
|
|
$in_parm = false;
|
|
} elseif (substr_count($line, '<name>')) {
|
|
$rrd['ds'][$ds_num]['name'] = XMLrip('name', $line);
|
|
|
|
$dsnames[] = XMLrip('name', $line);
|
|
} elseif (substr_count($line, '<type>')) {
|
|
$rrd['ds'][$ds_num]['type'] = XMLrip('type', $line);
|
|
} elseif (substr_count($line, '<minimal_heartbeat>')) {
|
|
$rrd['ds'][$ds_num]['minimal_heartbeat'] = XMLrip('minimal_heartbeat', $line);
|
|
} elseif (substr_count($line, '<max>')) {
|
|
$rrd['ds'][$ds_num]['max'] = XMLrip('max', $line);
|
|
} elseif (substr_count($line, '<min>')) {
|
|
$rrd['ds'][$ds_num]['min'] = XMLrip('min', $line);
|
|
} elseif (substr_count($line, '<last_ds>')) {
|
|
$rrd['ds'][$ds_num]['last_ds'] = XMLrip('last_ds', $line);
|
|
} elseif (substr_count($line, '<value>')) {
|
|
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, '<unknown_sec>')) {
|
|
$rrd['ds'][$ds_num]['unknown_sec'] = XMLrip('unknown_sec', $line);
|
|
} elseif (substr_count($line, '<cf>')) {
|
|
$rrd['rra'][$rra_num]['cf'] = XMLrip('cf', $line);
|
|
} elseif (substr_count($line, '<pdp_per_row>')) {
|
|
$rrd['rra'][$rra_num]['pdp_per_row'] = XMLrip('pdp_per_row', $line);
|
|
} elseif (substr_count($line, '<xff>')) {
|
|
$rrd['rra'][$rra_num]['params']['xff'] = XMLrip('xff', $line);
|
|
} elseif (substr_count($line, '<primary_value>')) {
|
|
$rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['primary_value'] = XMLrip('primary_value', $line);
|
|
} elseif (substr_count($line, '<secondary_value>')) {
|
|
$rrd['rra'][$rra_num]['cdp_prep'][$cdp_ds_num]['secondary_value'] = XMLrip('secondary_value', $line);
|
|
} elseif (substr_count($line, '<unknown_datapoints>')) {
|
|
$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(string $xmlfile, string $rrdfile) : void {
|
|
global $rrdtool;
|
|
|
|
// execute the dump command
|
|
print 'NOTE: Re-Importing \'' . $xmlfile . '\' to \'' . $rrdfile . '\'' . PHP_EOL;
|
|
$return_code = 0;
|
|
$output = [];
|
|
$command = "$rrdtool restore -f -r $xmlfile $rrdfile";
|
|
$result = exec($command, $output, $return_var);
|
|
|
|
if ($return_var == 0) {
|
|
print "NOTE: File $rrdfile Restored Correctly" . PHP_EOL;
|
|
} else {
|
|
print "WARNING: File $rrdfile Encountered Errors. Errors below:" . PHP_EOL;
|
|
|
|
foreach ($output as $l) {
|
|
print "WARNING: $l" . PHP_EOL;
|
|
}
|
|
}
|
|
}
|
|
|
|
function XMLrip(string $tag, string $line) : string {
|
|
return trim(str_replace("<$tag>", '', str_replace("</$tag>", '', $line)));
|
|
}
|
|
|
|
function writeXMLFile(string $output, string $xmlfile) : mixed {
|
|
return file_put_contents($xmlfile, $output);
|
|
}
|
|
|
|
function backupRRDFile(string $rrdfile) : bool {
|
|
global $tempdir, $html;
|
|
|
|
$backupdir = $tempdir;
|
|
|
|
/* the working directory is private to this run, so a name only has to be
|
|
* unique within it rather than unguessable */
|
|
$newfile = basename($rrdfile);
|
|
|
|
for ($i = 1; file_exists($backupdir . '/' . $newfile); $i++) {
|
|
$newfile = basename($rrdfile) . '.' . $i;
|
|
}
|
|
|
|
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 array $output - The output from the RRDtool command
|
|
*
|
|
* @return array - The updated XML object
|
|
*/
|
|
function preProcessXML(array &$output) : array {
|
|
$new_array = [];
|
|
|
|
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, '<!--');
|
|
|
|
if ($comment_start === false) {
|
|
// do nothing no line
|
|
} else {
|
|
$comment_end = strpos($line, '-->');
|
|
|
|
$row = strpos($line, '<row>');
|
|
|
|
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('<row>', "<row><date> $date </date>", $line);
|
|
}
|
|
}
|
|
|
|
if ($line != '') {
|
|
$new_array[] = $line;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// transfer the new array back to the original array
|
|
return $new_array;
|
|
}
|
|
|
|
/**
|
|
* Simple generic debug function
|
|
*
|
|
* @return void
|
|
*/
|
|
function debug(string $string) : void {
|
|
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.
|
|
*
|
|
* @return object - The SQLite memory database object
|
|
*/
|
|
function createTable() : object {
|
|
// 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 object $db
|
|
* @param array $records
|
|
*
|
|
* @return void
|
|
*/
|
|
function loadTable(object $db, array &$records) : void {
|
|
$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');
|
|
}
|
|
|
|
/**
|
|
* display_version - displays version information
|
|
*
|
|
* @return void
|
|
*/
|
|
function display_version() : void {
|
|
if (!defined('COPYRIGHT_YEARS')) {
|
|
define('COPYRIGHT_YEARS', '2004-2026');
|
|
}
|
|
|
|
$version = get_cacti_cli_version();
|
|
|
|
print 'Cacti RRDfile Splicer Utility, Version ' . $version . ', ' . COPYRIGHT_YEARS . PHP_EOL;
|
|
}
|
|
|
|
/**
|
|
* display_help - displays help information
|
|
*
|
|
* @return void
|
|
*/
|
|
function display_help() : void {
|
|
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 'Required:' . 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 . PHP_EOL;
|
|
|
|
print 'Optional:' . PHP_EOL;
|
|
print '--finrrd=file - The final RRDfile that contains data from both rrdfiles.' . 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;
|
|
}
|