2016-08-28 17:32:55 -05:00
#!/usr/bin/php -q
< ? php
/*
ex : set tabstop = 4 shiftwidth = 4 autoindent :
+-------------------------------------------------------------------------+
| Copyright ( C ) 2004 - 2009 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 / |
+-------------------------------------------------------------------------+
*/
/* do NOT run this script through a web browser */
2020-09-21 15:59:34 +02:00
if ( ! isset ( $_SERVER [ 'argv' ][ 0 ]) || isset ( $_SERVER [ 'REQUEST_METHOD' ]) || isset ( $_SERVER [ 'REMOTE_ADDR' ])) {
exit ( '<br><strong>This script is only meant to run at the command line.</strong>' );
2016-08-28 17:32:55 -05:00
}
/* We are not talking to the browser */
$no_http_headers = true ;
$dir = dirname ( __FILE__ );
chdir ( $dir );
if ( strpos ( $dir , 'spikekill' ) !== false ) {
chdir ( '../../' );
}
$using_cacti = false ;
/* setup defaults */
2020-09-21 14:54:51 +02:00
$debug = false ;
$dryrun = false ;
$avgnan = 'avg' ;
2020-09-21 15:59:34 +02:00
$rrdfile = '' ;
2016-08-28 17:32:55 -05:00
$std_kills = true ;
$var_kills = true ;
2020-09-21 14:54:51 +02:00
$html = false ;
2016-08-28 17:32:55 -05:00
if ( $using_cacti ) {
2020-09-21 15:59:34 +02:00
$method = read_config_option ( 'spikekill_method' );
$numspike = read_config_option ( 'spikekill_number' );
$stddev = read_config_option ( 'spikekill_deviations' );
$percent = read_config_option ( 'spikekill_percent' );
$outliers = read_config_option ( 'spikekill_outliers' );
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 14:54:51 +02:00
$method = 1 ; // Standard Deviation
2016-08-28 17:32:55 -05:00
$numspike = 10 ;
2020-09-21 14:54:51 +02:00
$stddev = 10 ;
$percent = 500 ;
2016-08-28 17:32:55 -05:00
$outliers = 5 ;
}
/* process calling arguments */
2020-09-21 15:59:34 +02:00
$parms = $_SERVER [ 'argv' ];
2016-08-28 17:32:55 -05:00
array_shift ( $parms );
foreach ( $parms as $parameter ) {
2020-09-21 15:59:34 +02:00
@ [ $arg , $value ] = @ explode ( '=' , $parameter );
2016-08-28 17:32:55 -05:00
switch ( $arg ) {
2020-09-21 15:59:34 +02:00
case '--method' :
case '-M' :
if ( $value == 'variance' ) {
2016-08-28 17:32:55 -05:00
$method = 2 ;
2020-09-21 15:59:34 +02:00
} elseif ( $value == 'stddev' ) {
2016-08-28 17:32:55 -05:00
$method = 1 ;
} else {
2020-09-21 14:54:51 +02:00
echo " FATAL: You must specify either 'stddev' or 'variance' as methods. \n \n " ;
display_help ();
exit ;
2016-08-28 17:32:55 -05:00
}
break ;
2020-09-21 15:59:34 +02:00
case '--avgnan' :
case '-A' :
if ( $value == 'avg' ) {
$avgnan = 'avg' ;
} elseif ( $value == 'nan' ) {
$avgnan = 'nan' ;
2016-08-28 17:32:55 -05:00
} else {
echo " FATAL: You must specify either 'avg' or 'nan' as replacement methods. \n \n " ;
display_help ();
exit ;
}
break ;
2020-09-21 15:59:34 +02:00
case '--rrdfile' :
case '-R' :
2016-08-28 17:32:55 -05:00
$rrdfile = $value ;
2020-09-21 14:54:51 +02:00
if ( ! file_exists ( $rrdfile )) {
2016-08-28 17:32:55 -05:00
echo " FATAL: File ' $rrdfile ' does not exist. \n " ;
exit ;
}
2020-09-21 14:54:51 +02:00
if ( ! is_writable ( $rrdfile )) {
2016-08-28 17:32:55 -05:00
echo " FATAL: File ' $rrdfile ' is not writable by this account. \n " ;
exit ;
}
break ;
2020-09-21 15:59:34 +02:00
case '--stddev' :
case '-S' :
2016-08-28 17:32:55 -05:00
$stddev = $value ;
2020-09-21 14:54:51 +02:00
if ( ! is_numeric ( $stddev ) || ( $stddev < 1 )) {
2016-08-28 17:32:55 -05:00
echo " FATAL: Standard Deviation must be a positive integer. \n \n " ;
display_help ();
exit ;
}
break ;
2020-09-21 15:59:34 +02:00
case '--outliers' :
case '-O' :
2016-08-28 17:32:55 -05:00
$outliers = $value ;
2020-09-21 14:54:51 +02:00
if ( ! is_numeric ( $outliers ) || ( $outliers < 1 )) {
2016-08-28 17:32:55 -05:00
echo " FATAL: The number of outliers to exlude must be a positive integer. \n \n " ;
display_help ();
exit ;
}
break ;
2020-09-21 15:59:34 +02:00
case '--percent' :
case '-P' :
2020-09-21 14:54:51 +02:00
$percent = $value / 100 ;
2016-08-28 17:32:55 -05:00
2020-09-21 14:54:51 +02:00
if ( ! is_numeric ( $percent ) || ( $percent <= 0 )) {
2016-08-28 17:32:55 -05:00
echo " FATAL: Percent deviation must be a positive floating point number. \n \n " ;
display_help ();
exit ;
}
break ;
2020-09-21 15:59:34 +02:00
case '--html' :
2016-08-28 17:32:55 -05:00
$html = true ;
break ;
2020-09-21 15:59:34 +02:00
case '-d' :
case '--debug' :
2016-08-28 17:32:55 -05:00
$debug = true ;
break ;
2020-09-21 15:59:34 +02:00
case '-D' :
case '--dryrun' :
2016-08-28 17:32:55 -05:00
$dryrun = true ;
break ;
2020-09-21 15:59:34 +02:00
case '--number' :
case '-n' :
2016-08-28 17:32:55 -05:00
$numspike = $value ;
2020-09-21 14:54:51 +02:00
if ( ! is_numeric ( $numspike ) || ( $numspike < 1 )) {
2016-08-28 17:32:55 -05:00
echo " FATAL: Number of spikes to remove must be a positive integer \n \n " ;
display_help ();
exit ;
}
break ;
2020-09-21 15:59:34 +02:00
case '-h' :
case '-v' :
case '-V' :
case '--version' :
case '--help' :
2016-08-28 17:32:55 -05:00
display_help ();
exit ;
default :
2020-09-21 15:59:34 +02:00
print 'ERROR: Invalid Parameter ' . $parameter . " \n \n " ;
2016-08-28 17:32:55 -05:00
display_help ();
exit ;
}
}
/* additional error check */
2020-09-21 15:59:34 +02:00
if ( $rrdfile == '' ) {
2016-08-28 17:32:55 -05:00
echo " FATAL: You must specify an RRDfile! \n \n " ;
display_help ();
exit ;
}
/* determine the temporary file name */
$seed = mt_rand ();
2020-09-21 15:59:34 +02:00
if ( $config [ 'cacti_server_os' ] == 'win32' ) {
$tempdir = getenv ( 'TEMP' );
$xmlfile = $tempdir . '/' . str_replace ( '.rrd' , '' , basename ( $rrdfile )) . '.dump.' . $seed ;
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 15:59:34 +02:00
$tempdir = '/tmp' ;
$xmlfile = '/tmp/' . str_replace ( '.rrd' , '' , basename ( $rrdfile )) . '.dump.' . $seed ;
2016-08-28 17:32:55 -05:00
}
if ( $html ) {
echo " <table cellpadding='3' cellspacing='0' class='spikekill_data' id='spikekill_data'> " ;
}
if ( $using_cacti ) {
2020-09-21 15:59:34 +02:00
cacti_log ( " NOTE: Removing Spikes for ' $rrdfile ', Method:' $method ' " , false , 'WEBUI' );
2016-08-28 17:32:55 -05:00
}
/* execute the dump command */
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . " NOTE: Creating XML file ' $xmlfile ' from ' $rrdfile ' " . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
if ( $using_cacti ) {
2020-09-21 15:59:34 +02:00
shell_exec ( read_config_option ( 'path_rrdtool' ) . " dump $rrdfile > $xmlfile " );
2016-08-28 17:32:55 -05:00
} else {
shell_exec ( " rrdtool dump $rrdfile > $xmlfile " );
}
/* read the xml file into an array*/
if ( file_exists ( $xmlfile )) {
$output = file ( $xmlfile );
/* remove the temp file */
unlink ( $xmlfile );
} else {
if ( $using_cacti ) {
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . 'FATAL: RRDtool Command Failed. Please verify that the RRDtool path is valid in Settings->Paths!' . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . 'FATAL: RRDtool Command Failed. Please insure RRDtool is in your path!' . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
}
exit ;
}
/* process the xml file and remove all comments */
$output = removeComments ( $output );
/* Read all the rra ' s ds values and obtain the following pieces of information from each
rra archive .
* numsamples - The number of 'valid' non - nan samples
* sumofsamples - The sum of all 'valid' samples .
* average - The average of all samples
* standard_deviation - The standard deviation of all samples
* max_value - The maximum value of all samples
* min_value - The minimum value of all samples
* max_cutoff - Any value above this value will be set to the average .
* min_cutoff - Any value lower than this value will be set to the average .
This will end up being a n - dimensional array as follows :
rra [ x ][ ds #]['totalsamples'];
rra [ x ][ ds #]['numsamples'];
rra [ x ][ ds #]['sumofsamples'];
rra [ x ][ ds #]['average'];
rra [ x ][ ds #]['stddev'];
rra [ x ][ ds #]['max_value'];
rra [ x ][ ds #]['min_value'];
rra [ x ][ ds #]['max_cutoff'];
rra [ x ][ ds #]['min_cutoff'];
There will also be a secondary array created with the actual samples . This
array will be used to calculate the standard deviation of the sample set .
samples [ rra_num ][ ds_num ][];
Also track the min and max value for each ds and store it into the two
arrays : ds_min [ ds #], ds_max[ds#].
The we don 't need to know the type of rra, only it' s number for this analysis
the same applies for the ds ' as well .
*/
2020-09-21 14:54:51 +02:00
$rra = [];
$rra_cf = [];
$rra_pdp = [];
2016-08-28 17:32:55 -05:00
$rra_num = 0 ;
2020-09-21 14:54:51 +02:00
$ds_num = 0 ;
2016-08-28 17:32:55 -05:00
$total_kills = 0 ;
2020-09-21 14:54:51 +02:00
$in_rra = false ;
$in_db = false ;
$ds_min = [];
$ds_max = [];
$ds_name = [];
2016-08-28 17:32:55 -05:00
/* perform a first pass on the array and do the following :
1 ) Get the number of good samples per ds
2 ) Get the sum of the samples per ds
3 ) Get the max and min values for all samples
4 ) Build both the rra and sample arrays
5 ) Get each ds ' min and max values
*/
if ( sizeof ( $output )) {
foreach ( $output as $line ) {
2020-09-21 15:59:34 +02:00
if ( substr_count ( $line , '<v>' )) {
$linearray = explode ( '<v>' , $line );
2016-08-28 17:32:55 -05:00
/* discard the row */
array_shift ( $linearray );
$ds_num = 0 ;
foreach ( $linearray as $dsvalue ) {
/* peel off garbage */
2020-09-21 15:59:34 +02:00
$dsvalue = trim ( str_replace ( '</row>' , '' , str_replace ( '</v>' , '' , $dsvalue )));
if ( strtolower ( $dsvalue ) != 'nan' ) {
if ( ! isset ( $rra [ $rra_num ][ $ds_num ][ 'numsamples' ])) {
$rra [ $rra_num ][ $ds_num ][ 'numsamples' ] = 1 ;
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'numsamples' ] ++ ;
2016-08-28 17:32:55 -05:00
}
2020-09-21 15:59:34 +02:00
if ( ! isset ( $rra [ $rra_num ][ $ds_num ][ 'sumofsamples' ])) {
$rra [ $rra_num ][ $ds_num ][ 'sumofsamples' ] = $dsvalue ;
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'sumofsamples' ] += $dsvalue ;
2016-08-28 17:32:55 -05:00
}
2020-09-21 15:59:34 +02:00
if ( ! isset ( $rra [ $rra_num ][ $ds_num ][ 'max_value' ])) {
$rra [ $rra_num ][ $ds_num ][ 'max_value' ] = $dsvalue ;
} elseif ( $dsvalue > $rra [ $rra_num ][ $ds_num ][ 'max_value' ]) {
$rra [ $rra_num ][ $ds_num ][ 'max_value' ] = $dsvalue ;
2016-08-28 17:32:55 -05:00
}
2020-09-21 15:59:34 +02:00
if ( ! isset ( $rra [ $rra_num ][ $ds_num ][ 'min_value' ])) {
$rra [ $rra_num ][ $ds_num ][ 'min_value' ] = $dsvalue ;
} elseif ( $dsvalue < $rra [ $rra_num ][ $ds_num ][ 'min_value' ]) {
$rra [ $rra_num ][ $ds_num ][ 'min_value' ] = $dsvalue ;
2016-08-28 17:32:55 -05:00
}
/* store the sample for standard deviation calculation */
$samples [ $rra_num ][ $ds_num ][] = $dsvalue ;
}
2020-09-21 15:59:34 +02:00
if ( ! isset ( $rra [ $rra_num ][ $ds_num ][ 'totalsamples' ])) {
$rra [ $rra_num ][ $ds_num ][ 'totalsamples' ] = 1 ;
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'totalsamples' ] ++ ;
2016-08-28 17:32:55 -05:00
}
$ds_num ++ ;
}
2020-09-21 15:59:34 +02:00
} elseif ( substr_count ( $line , '<rra>' )) {
2016-08-28 17:32:55 -05:00
$in_rra = true ;
2020-09-21 15:59:34 +02:00
} elseif ( substr_count ( $line , '<min>' )) {
$ds_min [] = trim ( str_replace ( '<min>' , '' , str_replace ( '</min>' , '' , trim ( $line ))));
} elseif ( substr_count ( $line , '<max>' )) {
$ds_max [] = trim ( str_replace ( '<max>' , '' , str_replace ( '</max>' , '' , trim ( $line ))));
} elseif ( substr_count ( $line , '<name>' )) {
$ds_name [] = trim ( str_replace ( '<name>' , '' , str_replace ( '</name>' , '' , trim ( $line ))));
} elseif ( substr_count ( $line , '<cf>' )) {
$rra_cf [] = trim ( str_replace ( '<cf>' , '' , str_replace ( '</cf>' , '' , trim ( $line ))));
} elseif ( substr_count ( $line , '<pdp_per_row>' )) {
$rra_pdp [] = trim ( str_replace ( '<pdp_per_row>' , '' , str_replace ( '</pdp_per_row>' , '' , trim ( $line ))));
} elseif ( substr_count ( $line , '</rra>' )) {
2016-08-28 17:32:55 -05:00
$in_rra = false ;
$rra_num ++ ;
2020-09-21 15:59:34 +02:00
} elseif ( substr_count ( $line , '<step>' )) {
$step = trim ( str_replace ( '<step>' , '' , str_replace ( '</step>' , '' , trim ( $line ))));
2016-08-28 17:32:55 -05:00
}
}
}
/* For all the samples determine the average with the outliers removed */
calculateVarianceAverages ( $rra , $samples );
/* Now scan the rra array and the samples array and calculate the following
1 ) The standard deviation of all samples
2 ) The average of all samples per ds
3 ) The max and min cutoffs of all samples
4 ) The number of kills in each ds based upon the thresholds
*/
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . " NOTE: Searching for Spikes in XML file ' $xmlfile ' " . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
calculateOverallStatistics ( $rra , $samples );
/* debugging and/or status report */
if ( $debug || $dryrun ) {
outputStatistics ( $rra );
}
/* create an output array */
if ( $method == 1 ) {
/* standard deviation subroutine */
if ( $std_kills ) {
2020-09-21 14:54:51 +02:00
if ( ! $dryrun ) {
2016-08-28 17:32:55 -05:00
$new_output = updateXML ( $output , $rra );
}
} else {
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . " NOTE: NO Standard Deviation Spikes found in ' $rrdfile ' " . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
}
} else {
/* variance subroutine */
if ( $var_kills ) {
2020-09-21 14:54:51 +02:00
if ( ! $dryrun ) {
2016-08-28 17:32:55 -05:00
$new_output = updateXML ( $output , $rra );
}
} else {
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . " NOTE: NO Variance Spikes found in ' $rrdfile ' " . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
}
}
/* finally update the file XML file and Reprocess the RRDfile */
2020-09-21 14:54:51 +02:00
if ( ! $dryrun ) {
2016-08-28 17:32:55 -05:00
if ( $total_kills ) {
if ( writeXMLFile ( $new_output , $xmlfile )) {
if ( backupRRDFile ( $rrdfile )) {
createRRDFileFromXML ( $xmlfile , $rrdfile );
} else {
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . " FATAL: Unable to backup ' $rrdfile ' " . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
}
} else {
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . " FATAL: Unable to write XML file ' $xmlfile ' " . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
}
}
} else {
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . 'NOTE: Dryrun requested. No updates performed' . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
}
if ( $html ) {
2020-09-21 15:59:34 +02:00
echo '</table>' ;
2016-08-28 17:32:55 -05:00
}
/* All Functions */
function createRRDFileFromXML ( $xmlfile , $rrdfile )
{
global $using_cacti , $html ;
/* execute the dump command */
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . " NOTE: Re-Importing ' $xmlfile ' to ' $rrdfile ' " . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
if ( $using_cacti ) {
2020-09-21 15:59:34 +02:00
$response = shell_exec ( read_config_option ( 'path_rrdtool' ) . " restore -f -r $xmlfile $rrdfile " );
2016-08-28 17:32:55 -05:00
} else {
$response = shell_exec ( " rrdtool restore -f -r $xmlfile $rrdfile " );
}
if ( strlen ( $response )) {
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . $response . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
}
}
function writeXMLFile ( $output , $xmlfile )
{
return file_put_contents ( $xmlfile , $output );
}
function backupRRDFile ( $rrdfile )
{
global $using_cacti , $tempdir , $seed , $html ;
if ( $using_cacti ) {
2020-09-21 15:59:34 +02:00
$backupdir = read_config_option ( 'spikekill_backupdir' );
2016-08-28 17:32:55 -05:00
2020-09-21 15:59:34 +02:00
if ( $backupdir == '' ) {
2016-08-28 17:32:55 -05:00
$backupdir = $tempdir ;
}
} else {
$backupdir = $tempdir ;
}
2020-09-21 15:59:34 +02:00
if ( file_exists ( $backupdir . '/' . basename ( $rrdfile ))) {
$newfile = basename ( $rrdfile ) . '.' . $seed ;
2016-08-28 17:32:55 -05:00
} else {
$newfile = basename ( $rrdfile );
}
2020-09-21 15:59:34 +02:00
echo ( $html ? " <tr><td colspan='20' class='spikekill_note'> " : '' ) . " NOTE: Backing Up ' $rrdfile ' to ' " . $backupdir . '/' . $newfile . " ' " . ( $html ? " </td></tr> \n " : " \n " );
2016-08-28 17:32:55 -05:00
2020-09-21 15:59:34 +02:00
return copy ( $rrdfile , $backupdir . '/' . $newfile );
2016-08-28 17:32:55 -05:00
}
function calculateVarianceAverages ( & $rra , & $samples )
{
global $outliers ;
if ( sizeof ( $samples )) {
foreach ( $samples as $rra_num => $dses ) {
if ( sizeof ( $dses )) {
foreach ( $dses as $ds_num => $ds ) {
if ( sizeof ( $ds ) < $outliers * 3 ) {
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'variance_avg' ] = 'NAN' ;
2016-08-28 17:32:55 -05:00
} else {
rsort ( $ds , SORT_NUMERIC );
$ds = array_slice ( $ds , $outliers );
sort ( $ds , SORT_NUMERIC );
$ds = array_slice ( $ds , $outliers );
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'variance_avg' ] = array_sum ( $ds ) / sizeof ( $ds );
2016-08-28 17:32:55 -05:00
}
}
}
}
}
}
function calculateOverallStatistics ( & $rra , & $samples )
{
global $percent , $stddev , $ds_min , $ds_max , $var_kills , $std_kills ;
$rra_num = 0 ;
if ( sizeof ( $rra )) {
foreach ( $rra as $dses ) {
$ds_num = 0 ;
if ( sizeof ( $dses )) {
foreach ( $dses as $ds ) {
if ( isset ( $samples [ $rra_num ][ $ds_num ])) {
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'standard_deviation' ] = standard_deviation ( $samples [ $rra_num ][ $ds_num ]);
if ( $rra [ $rra_num ][ $ds_num ][ 'standard_deviation' ] == 'NAN' ) {
$rra [ $rra_num ][ $ds_num ][ 'standard_deviation' ] = 0 ;
2016-08-28 17:32:55 -05:00
}
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'average' ] = $rra [ $rra_num ][ $ds_num ][ 'sumofsamples' ] / $rra [ $rra_num ][ $ds_num ][ 'numsamples' ];
2016-08-28 17:32:55 -05:00
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'min_cutoff' ] = $rra [ $rra_num ][ $ds_num ][ 'average' ] - ( $stddev * $rra [ $rra_num ][ $ds_num ][ 'standard_deviation' ]);
if ( $rra [ $rra_num ][ $ds_num ][ 'min_cutoff' ] < $ds_min [ $ds_num ]) {
$rra [ $rra_num ][ $ds_num ][ 'min_cutoff' ] = $ds_min [ $ds_num ];
2016-08-28 17:32:55 -05:00
}
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'max_cutoff' ] = $rra [ $rra_num ][ $ds_num ][ 'average' ] + ( $stddev * $rra [ $rra_num ][ $ds_num ][ 'standard_deviation' ]);
if ( $rra [ $rra_num ][ $ds_num ][ 'max_cutoff' ] > $ds_max [ $ds_num ]) {
$rra [ $rra_num ][ $ds_num ][ 'max_cutoff' ] = $ds_max [ $ds_num ];
2016-08-28 17:32:55 -05:00
}
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'numnksamples' ] = 0 ;
$rra [ $rra_num ][ $ds_num ][ 'sumnksamples' ] = 0 ;
$rra [ $rra_num ][ $ds_num ][ 'avgnksamples' ] = 0 ;
2016-08-28 17:32:55 -05:00
/* go through values and find cutoffs */
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'stddev_killed' ] = 0 ;
$rra [ $rra_num ][ $ds_num ][ 'variance_killed' ] = 0 ;
2016-08-28 17:32:55 -05:00
if ( sizeof ( $samples [ $rra_num ][ $ds_num ])) {
foreach ( $samples [ $rra_num ][ $ds_num ] as $sample ) {
2020-09-21 15:59:34 +02:00
if (( $sample > $rra [ $rra_num ][ $ds_num ][ 'max_cutoff' ]) ||
( $sample < $rra [ $rra_num ][ $ds_num ][ 'min_cutoff' ])) {
debug ( sprintf ( " Std Kill: Value '%.4e', StandardDev '%.4e', StdDevLimit '%.4e' " , $sample , $rra [ $rra_num ][ $ds_num ][ 'standard_deviation' ], ( $rra [ $rra_num ][ $ds_num ][ 'max_cutoff' ] * ( 1 + $percent ))));
$rra [ $rra_num ][ $ds_num ][ 'stddev_killed' ] ++ ;
2016-08-28 17:32:55 -05:00
$std_kills = true ;
} else {
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'numnksamples' ] ++ ;
$rra [ $rra_num ][ $ds_num ][ 'sumnksamples' ] += $sample ;
2016-08-28 17:32:55 -05:00
}
2020-09-21 15:59:34 +02:00
if ( $rra [ $rra_num ][ $ds_num ][ 'variance_avg' ] == 'NAN' ) {
2016-08-28 17:32:55 -05:00
/* not enought samples to calculate */
2020-09-21 15:59:34 +02:00
} elseif ( $sample > ( $rra [ $rra_num ][ $ds_num ][ 'variance_avg' ] * ( 1 + $percent ))) {
2016-08-28 17:32:55 -05:00
/* kill based upon variance */
2020-09-21 15:59:34 +02:00
debug ( sprintf ( " Var Kill: Value '%.4e', VarianceDev '%.4e', VarianceLimit '%.4e' " , $sample , $rra [ $rra_num ][ $ds_num ][ 'variance_avg' ], ( $rra [ $rra_num ][ $ds_num ][ 'variance_avg' ] * ( 1 + $percent ))));
$rra [ $rra_num ][ $ds_num ][ 'variance_killed' ] ++ ;
2016-08-28 17:32:55 -05:00
$var_kills = true ;
}
}
}
2020-09-21 15:59:34 +02:00
if ( $rra [ $rra_num ][ $ds_num ][ 'numnksamples' ] > 0 ) {
$rra [ $rra_num ][ $ds_num ][ 'avgnksamples' ] = $rra [ $rra_num ][ $ds_num ][ 'sumnksamples' ] / $rra [ $rra_num ][ $ds_num ][ 'numnksamples' ];
2016-08-28 17:32:55 -05:00
}
} else {
2020-09-21 15:59:34 +02:00
$rra [ $rra_num ][ $ds_num ][ 'standard_deviation' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'average' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'min_cutoff' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'max_cutoff' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'numnksamples' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'sumnksamples' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'avgnksamples' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'stddev_killed' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'variance_killed' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'stddev_killed' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'numnksamples' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'sumnksamples' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'variance_killed' ] = 'N/A' ;
$rra [ $rra_num ][ $ds_num ][ 'avgnksamples' ] = 'N/A' ;
2016-08-28 17:32:55 -05:00
}
2020-09-21 14:54:51 +02:00
$ds_num ++ ;
2016-08-28 17:32:55 -05:00
}
}
$rra_num ++ ;
}
}
}
function outputStatistics ( $rra )
{
global $rra_cf , $rra_name , $ds_name , $rra_pdp , $html ;
if ( sizeof ( $rra )) {
2020-09-21 14:54:51 +02:00
if ( ! $html ) {
2016-08-28 17:32:55 -05:00
echo " \n " ;
printf (
" %10s %16s %10s %7s %7s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s \n " ,
2020-09-21 15:59:34 +02:00
'Size' ,
'DataSource' ,
'CF' ,
'Samples' ,
'NonNan' ,
'Avg' ,
'StdDev' ,
'MaxValue' ,
'MinValue' ,
'MaxStdDev' ,
'MinStdDev' ,
'StdKilled' ,
'VarKilled' ,
'StdDevAvg' ,
'VarAvg'
2016-08-28 17:32:55 -05:00
);
printf (
" %10s %16s %10s %7s %7s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s \n " ,
2020-09-21 15:59:34 +02:00
'----------' ,
'---------------' ,
'----------' ,
'-------' ,
'-------' ,
'----------' ,
'----------' ,
'----------' ,
'----------' ,
'----------' ,
'----------' ,
'----------' ,
'----------' ,
'----------' ,
'----------'
2016-08-28 17:32:55 -05:00
);
foreach ( $rra as $rra_key => $dses ) {
if ( sizeof ( $dses )) {
foreach ( $dses as $dskey => $ds ) {
printf (
2020-09-21 15:59:34 +02:00
'%10s %16s %10s %7s %7s ' .
( $ds [ 'average' ] < 1E6 ? '%10s ' : '%10.4e ' ) .
( $ds [ 'standard_deviation' ] < 1E6 ? '%10s ' : '%10.4e ' ) .
( isset ( $ds [ 'max_value' ]) ? ( $ds [ 'max_value' ] < 1E6 ? '%10s ' : '%10.4e ' ) : '%10s ' ) .
( isset ( $ds [ 'min_value' ]) ? ( $ds [ 'min_value' ] < 1E6 ? '%10s ' : '%10.4e ' ) : '%10s ' ) .
( isset ( $ds [ 'max_cutoff' ]) ? ( $ds [ 'max_cutoff' ] < 1E6 ? '%10s ' : '%10.4e ' ) : '%10s ' ) .
( isset ( $ds [ 'min_cutoff' ]) ? ( $ds [ 'min_cutoff' ] < 1E6 ? '%10s ' : '%10.4e ' ) : '%10s ' ) .
'%10s %10s ' .
( isset ( $ds [ 'avgnksampled' ]) ? ( $ds [ 'avgnksamples' ] < 1E6 ? '%10s ' : '%10.4e ' ) : '%10s ' ) .
( isset ( $ds [ 'variance_avg' ]) ? ( $ds [ 'variance_avg' ] < 1E6 ? '%10s ' : '%10.4e ' ) : '%10s ' ) . " \n " ,
2016-08-28 17:32:55 -05:00
displayTime ( $rra_pdp [ $rra_key ]),
$ds_name [ $dskey ],
$rra_cf [ $rra_key ],
2020-09-21 15:59:34 +02:00
$ds [ 'totalsamples' ],
( isset ( $ds [ 'numsamples' ]) ? $ds [ 'numsamples' ] : '0' ),
( $ds [ 'average' ] != 'N/A' ? round ( $ds [ 'average' ], 2 ) : $ds [ 'average' ]),
( $ds [ 'standard_deviation' ] != 'N/A' ? round ( $ds [ 'standard_deviation' ], 2 ) : $ds [ 'standard_deviation' ]),
( isset ( $ds [ 'max_value' ]) ? round ( $ds [ 'max_value' ], 2 ) : 'N/A' ),
( isset ( $ds [ 'min_value' ]) ? round ( $ds [ 'min_value' ], 2 ) : 'N/A' ),
( $ds [ 'max_cutoff' ] != 'N/A' ? round ( $ds [ 'max_cutoff' ], 2 ) : $ds [ 'max_cutoff' ]),
( $ds [ 'min_cutoff' ] != 'N/A' ? round ( $ds [ 'min_cutoff' ], 2 ) : $ds [ 'min_cutoff' ]),
$ds [ 'stddev_killed' ],
$ds [ 'variance_killed' ],
( $ds [ 'avgnksamples' ] != 'N/A' ? round ( $ds [ 'avgnksamples' ], 2 ) : $ds [ 'avgnksamples' ]),
( isset ( $ds [ 'variance_avg' ]) ? round ( $ds [ 'variance_avg' ], 2 ) : 'N/A' )
2016-08-28 17:32:55 -05:00
);
}
}
}
echo " \n " ;
} else {
printf (
" <tr><th style='width:10%%;'>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr> \n " ,
2020-09-21 15:59:34 +02:00
'Size' ,
'DataSource' ,
'CF' ,
'Samples' ,
'NonNan' ,
'Avg' ,
'StdDev' ,
'MaxValue' ,
'MinValue' ,
'MaxStdDev' ,
'MinStdDev' ,
'StdKilled' ,
'VarKilled' ,
'StdDevAvg' ,
'VarAvg'
2016-08-28 17:32:55 -05:00
);
foreach ( $rra as $rra_key => $dses ) {
if ( sizeof ( $dses )) {
foreach ( $dses as $dskey => $ds ) {
printf (
2020-09-21 15:59:34 +02:00
'<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>' .
( $ds [ 'average' ] < 1E6 ? '%s</td><td>' : '%.4e</td><td>' ) .
( $ds [ 'standard_deviation' ] < 1E6 ? '%s</td><td>' : '%.4e</td><td>' ) .
( isset ( $ds [ 'max_value' ]) ? ( $ds [ 'max_value' ] < 1E6 ? '%s</td><td>' : '%.4e</td><td>' ) : '%s</td><td>' ) .
( isset ( $ds [ 'min_value' ]) ? ( $ds [ 'min_value' ] < 1E6 ? '%s</td><td>' : '%.4e</td><td>' ) : '%s</td><td>' ) .
( isset ( $ds [ 'max_cutoff' ]) ? ( $ds [ 'max_cutoff' ] < 1E6 ? '%s</td><td>' : '%.4e</td><td>' ) : '%s</td><td>' ) .
( isset ( $ds [ 'min_cutoff' ]) ? ( $ds [ 'min_cutoff' ] < 1E6 ? '%s</td><td>' : '%.4e</td><td>' ) : '%s</td><td>' ) .
'%s</td><td>%s</td><td>' .
( isset ( $ds [ 'avgnksampled' ]) ? ( $ds [ 'avgnksamples' ] < 1E6 ? '%s</td><td>' : '%.4e</td><td>' ) : '%s</td><td>' ) .
( isset ( $ds [ 'variance_avg' ]) ? ( $ds [ 'variance_avg' ] < 1E6 ? " %s</td></tr> \n " : " %.4e</td></tr> \n " ) : " %s</td></tr> \n " ) . " \n " ,
2016-08-28 17:32:55 -05:00
displayTime ( $rra_pdp [ $rra_key ]),
$ds_name [ $dskey ],
$rra_cf [ $rra_key ],
2020-09-21 15:59:34 +02:00
$ds [ 'totalsamples' ],
( isset ( $ds [ 'numsamples' ]) ? $ds [ 'numsamples' ] : '0' ),
( $ds [ 'average' ] != 'N/A' ? round ( $ds [ 'average' ], 2 ) : $ds [ 'average' ]),
( $ds [ 'standard_deviation' ] != 'N/A' ? round ( $ds [ 'standard_deviation' ], 2 ) : $ds [ 'standard_deviation' ]),
( isset ( $ds [ 'max_value' ]) ? round ( $ds [ 'max_value' ], 2 ) : 'N/A' ),
( isset ( $ds [ 'min_value' ]) ? round ( $ds [ 'min_value' ], 2 ) : 'N/A' ),
( $ds [ 'max_cutoff' ] != 'N/A' ? round ( $ds [ 'max_cutoff' ], 2 ) : $ds [ 'max_cutoff' ]),
( $ds [ 'min_cutoff' ] != 'N/A' ? round ( $ds [ 'min_cutoff' ], 2 ) : $ds [ 'min_cutoff' ]),
$ds [ 'stddev_killed' ],
$ds [ 'variance_killed' ],
( $ds [ 'avgnksamples' ] != 'N/A' ? round ( $ds [ 'avgnksamples' ], 2 ) : $ds [ 'avgnksamples' ]),
( isset ( $ds [ 'variance_avg' ]) ? round ( $ds [ 'variance_avg' ], 2 ) : 'N/A' )
2016-08-28 17:32:55 -05:00
);
}
}
}
}
}
}
function updateXML ( & $output , & $rra )
{
global $numspike , $percent , $avgnan , $method , $total_kills ;
2020-09-21 14:54:51 +02:00
$new_array = [];
2016-08-28 17:32:55 -05:00
/* variance subroutine */
$rra_num = 0 ;
2020-09-21 14:54:51 +02:00
$ds_num = 0 ;
$kills = 0 ;
2016-08-28 17:32:55 -05:00
if ( sizeof ( $output )) {
foreach ( $output as $line ) {
2020-09-21 15:59:34 +02:00
if ( substr_count ( $line , '<v>' )) {
$linearray = explode ( '<v>' , $line );
2016-08-28 17:32:55 -05:00
/* discard the row */
array_shift ( $linearray );
/* initialize variables */
2020-09-21 14:54:51 +02:00
$ds_num = 0 ;
2020-09-21 15:59:34 +02:00
$out_row = '<row>' ;
2016-08-28 17:32:55 -05:00
foreach ( $linearray as $dsvalue ) {
/* peel off garbage */
2020-09-21 15:59:34 +02:00
$dsvalue = trim ( str_replace ( '</row>' , '' , str_replace ( '</v>' , '' , $dsvalue )));
if ( strtolower ( $dsvalue ) == 'nan' ) {
2016-08-28 17:32:55 -05:00
/* do nothing, it's a NaN */
} else {
if ( $method == 2 ) {
2020-09-21 15:59:34 +02:00
if ( $dsvalue > ( 1 + $percent ) * $rra [ $rra_num ][ $ds_num ][ 'variance_avg' ]) {
2016-08-28 17:32:55 -05:00
if ( $kills < $numspike ) {
2020-09-21 15:59:34 +02:00
if ( $avgnan == 'avg' ) {
$dsvalue = $rra [ $rra_num ][ $ds_num ][ 'variance_avg' ];
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 15:59:34 +02:00
$dsvalue = 'NaN' ;
2016-08-28 17:32:55 -05:00
}
$kills ++ ;
$total_kills ++ ;
}
}
} else {
2020-09-21 15:59:34 +02:00
if (( $dsvalue > $rra [ $rra_num ][ $ds_num ][ 'max_cutoff' ]) ||
( $dsvalue < $rra [ $rra_num ][ $ds_num ][ 'min_cutoff' ])) {
2016-08-28 17:32:55 -05:00
if ( $kills < $numspike ) {
2020-09-21 15:59:34 +02:00
if ( $avgnan == 'avg' ) {
$dsvalue = $rra [ $rra_num ][ $ds_num ][ 'average' ];
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 15:59:34 +02:00
$dsvalue = 'NaN' ;
2016-08-28 17:32:55 -05:00
}
$kills ++ ;
$total_kills ++ ;
}
}
}
}
2020-09-21 15:59:34 +02:00
$out_row .= '<v> ' . $dsvalue . '</v>' ;
2016-08-28 17:32:55 -05:00
$ds_num ++ ;
}
2020-09-21 15:59:34 +02:00
$out_row .= '</row>' ;
2016-08-28 17:32:55 -05:00
$new_array [] = $out_row ;
} else {
2020-09-21 15:59:34 +02:00
if ( substr_count ( $line , '</rra>' )) {
2020-09-21 14:54:51 +02:00
$ds_minmax = [];
2016-08-28 17:32:55 -05:00
$rra_num ++ ;
$kills = 0 ;
2020-09-21 15:59:34 +02:00
} elseif ( substr_count ( $line , '</database>' )) {
2016-08-28 17:32:55 -05:00
$ds_num ++ ;
$kills = 0 ;
}
$new_array [] = $line ;
}
}
}
return $new_array ;
}
function removeComments ( & $output )
{
2020-09-21 14:54:51 +02:00
$new_array = [];
2016-08-28 17:32:55 -05:00
if ( sizeof ( $output )) {
foreach ( $output as $line ) {
$line = trim ( $line );
2020-09-21 15:59:34 +02:00
if ( $line == '' ) {
2016-08-28 17:32:55 -05:00
continue ;
} else {
/* is there a comment, remove it */
2020-09-21 15:59:34 +02:00
$comment_start = strpos ( $line , '<!--' );
2016-08-28 17:32:55 -05:00
if ( $comment_start === false ) {
/* do nothing no line */
} else {
2020-09-21 15:59:34 +02:00
$comment_end = strpos ( $line , '-->' );
2016-08-28 17:32:55 -05:00
if ( $comment_start == 0 ) {
2020-09-21 14:54:51 +02:00
$line = trim ( substr ( $line , $comment_end + 3 ));
2016-08-28 17:32:55 -05:00
} else {
2020-09-21 14:54:51 +02:00
$line = trim ( substr ( $line , 0 , $comment_start - 1 ) . substr ( $line , $comment_end + 3 ));
2016-08-28 17:32:55 -05:00
}
}
2020-09-21 15:59:34 +02:00
if ( $line != '' ) {
2016-08-28 17:32:55 -05:00
$new_array [] = $line ;
}
}
}
/* transfer the new array back to the original array */
return $new_array ;
}
}
function displayTime ( $pdp )
{
global $step ;
$total_time = $pdp * $step ; // seconds
if ( $total_time < 60 ) {
2020-09-21 15:59:34 +02:00
return $total_time . ' secs' ;
2016-08-28 17:32:55 -05:00
} else {
$total_time = $total_time / 60 ;
if ( $total_time < 60 ) {
2020-09-21 15:59:34 +02:00
return $total_time . ' mins' ;
2016-08-28 17:32:55 -05:00
} else {
$total_time = $total_time / 60 ;
if ( $total_time < 24 ) {
2020-09-21 15:59:34 +02:00
return $total_time . ' hours' ;
2016-08-28 17:32:55 -05:00
} else {
$total_time = $total_time / 24 ;
2020-09-21 15:59:34 +02:00
return $total_time . ' days' ;
2016-08-28 17:32:55 -05:00
}
}
}
}
function debug ( $string )
{
global $debug ;
if ( $debug ) {
2020-09-21 15:59:34 +02:00
echo 'DEBUG: ' . $string . " \n " ;
2016-08-28 17:32:55 -05:00
}
}
function standard_deviation ( $samples )
{
$sample_count = count ( $samples );
2020-09-21 14:54:51 +02:00
$sample_square = [];
2016-08-28 17:32:55 -05:00
2020-09-21 14:54:51 +02:00
for ( $current_sample = 0 ; $sample_count > $current_sample ; $current_sample ++ ) {
2016-08-28 17:32:55 -05:00
$sample_square [ $current_sample ] = pow ( $samples [ $current_sample ], 2 );
}
return sqrt ( array_sum ( $sample_square ) / $sample_count - pow (( array_sum ( $samples ) / $sample_count ), 2 ));
}
/* display_help - displays the usage of the function */
function display_help ()
{
global $using_cacti ;
if ( $using_cacti ) {
$version = spikekill_version ();
} else {
2020-09-21 15:59:34 +02:00
$version = 'v1.0' ;
2016-08-28 17:32:55 -05:00
}
2020-09-21 15:59:34 +02:00
echo 'Cacti Spike Remover ' . ( $using_cacti ? 'v' . $version [ 'version' ] : $version ) . " , Copyright 2009, The Cacti Group, Inc. \n \n " ;
2016-08-28 17:32:55 -05:00
echo " Usage: \n " ;
echo " removespikes.php -R|--rrdfile=rrdfile [-M|--method=stddev] [-A|--avgnan] [-S|--stddev=N] \n " ;
echo " [-P|--percent=N] [-N|--number=N] [-D|--dryrun] [-d|--debug] [-h|--help|-v|-V|--version] \n \n " ;
echo " The RRDfile input parameter is mandatory. If no other input parameters are specified the defaults \n " ;
echo " are taken from the Spikekill Plugin settings. \n \n " ;
echo " -M|--method - The spike removal method to use. Options are 'stddev'|'variance' \n " ;
echo " -A|--avgnan - The spike replacement method to use. Options are 'avg'|'nan' \n " ;
echo " -S|--stddev - The number of standard deviations +/- allowed \n " ;
echo " -P|--percent - The sample to sample percentage variation allowed \n " ;
echo " -N|--number - The maximum number of spikes to remove from the RRDfile \n " ;
echo " -D|--dryrun - If specified, the RRDfile will not be changed. Instead a summary of \n " ;
echo " changes that would have been performed will be issued. \n \n " ;
echo " The remainder of arguments are informational \n " ;
echo " -d|--debug - Display verbose output during execution \n " ;
echo " -v -V --version - Display this help message \n " ;
echo " -h --help - display this help message \n " ;
}