mirror of
				https://github.com/librenms/librenms.git
				synced 2024-10-07 16:52:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			904 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			904 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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 */
 | |
| if (!isset($_SERVER["argv"][0]) || isset($_SERVER['REQUEST_METHOD'])  || isset($_SERVER['REMOTE_ADDR'])) {
 | |
|     die("<br><strong>This script is only meant to run at the command line.</strong>");
 | |
| }
 | |
| 
 | |
| /* 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 */
 | |
| $debug     = false;
 | |
| $dryrun    = false;
 | |
| $avgnan    = 'avg';
 | |
| $rrdfile   = "";
 | |
| $std_kills = true;
 | |
| $var_kills = true;
 | |
| $html      = false;
 | |
| 
 | |
| if ($using_cacti) {
 | |
|     $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");
 | |
| } else {
 | |
|     $method   = 1; // Standard Deviation
 | |
|     $numspike = 10;
 | |
|     $stddev   = 10;
 | |
|     $percent  = 500;
 | |
|     $outliers = 5;
 | |
| }
 | |
| 
 | |
| /* process calling arguments */
 | |
| $parms = $_SERVER["argv"];
 | |
| array_shift($parms);
 | |
| 
 | |
| foreach ($parms as $parameter) {
 | |
|     @list($arg, $value) = @explode("=", $parameter);
 | |
| 
 | |
|     switch ($arg) {
 | |
|         case "--method":
 | |
|         case "-M":
 | |
|             if ($value == "variance") {
 | |
|                 $method = 2;
 | |
|             } elseif ($value == "stddev") {
 | |
|                 $method = 1;
 | |
|             } else {
 | |
|                     echo "FATAL: You must specify either 'stddev' or 'variance' as methods.\n\n";
 | |
|                     display_help();
 | |
|                     exit;
 | |
|             }
 | |
| 
 | |
|             break;
 | |
|         case "--avgnan":
 | |
|         case "-A":
 | |
|             if ($value == "avg") {
 | |
|                 $avgnan = "avg";
 | |
|             } elseif ($value == "nan") {
 | |
|                 $avgnan = "nan";
 | |
|             } else {
 | |
|                 echo "FATAL: You must specify either 'avg' or 'nan' as replacement methods.\n\n";
 | |
|                 display_help();
 | |
|                 exit;
 | |
|             }
 | |
| 
 | |
|             break;
 | |
|         case "--rrdfile":
 | |
|         case "-R":
 | |
|             $rrdfile = $value;
 | |
| 
 | |
|             if (!file_exists($rrdfile)) {
 | |
|                 echo "FATAL: File '$rrdfile' does not exist.\n";
 | |
|                 exit;
 | |
|             }
 | |
| 
 | |
|             if (!is_writable($rrdfile)) {
 | |
|                 echo "FATAL: File '$rrdfile' is not writable by this account.\n";
 | |
|                 exit;
 | |
|             }
 | |
| 
 | |
|             break;
 | |
|         case "--stddev":
 | |
|         case "-S":
 | |
|             $stddev = $value;
 | |
| 
 | |
|             if (!is_numeric($stddev) || ($stddev < 1)) {
 | |
|                 echo "FATAL: Standard Deviation must be a positive integer.\n\n";
 | |
|                 display_help();
 | |
|                 exit;
 | |
|             }
 | |
| 
 | |
|             break;
 | |
|         case "--outliers":
 | |
|         case "-O":
 | |
|             $outliers = $value;
 | |
| 
 | |
|             if (!is_numeric($outliers) || ($outliers < 1)) {
 | |
|                 echo "FATAL: The number of outliers to exlude must be a positive integer.\n\n";
 | |
|                 display_help();
 | |
|                 exit;
 | |
|             }
 | |
| 
 | |
|             break;
 | |
|         case "--percent":
 | |
|         case "-P":
 | |
|             $percent = $value/100;
 | |
| 
 | |
|             if (!is_numeric($percent) || ($percent <= 0)) {
 | |
|                 echo "FATAL: Percent deviation must be a positive floating point number.\n\n";
 | |
|                 display_help();
 | |
|                 exit;
 | |
|             }
 | |
| 
 | |
|             break;
 | |
|         case "--html":
 | |
|             $html = true;
 | |
| 
 | |
|             break;
 | |
|         case "-d":
 | |
|         case "--debug":
 | |
|             $debug = true;
 | |
| 
 | |
|             break;
 | |
|         case "-D":
 | |
|         case "--dryrun":
 | |
|             $dryrun = true;
 | |
| 
 | |
|             break;
 | |
|         case "--number":
 | |
|         case "-n":
 | |
|             $numspike = $value;
 | |
| 
 | |
|             if (!is_numeric($numspike) || ($numspike < 1)) {
 | |
|                 echo "FATAL: Number of spikes to remove must be a positive integer\n\n";
 | |
|                 display_help();
 | |
|                 exit;
 | |
|             }
 | |
| 
 | |
|             break;
 | |
|         case "-h":
 | |
|         case "-v":
 | |
|         case "-V":
 | |
|         case "--version":
 | |
|         case "--help":
 | |
|             display_help();
 | |
|             exit;
 | |
|         default:
 | |
|             print "ERROR: Invalid Parameter " . $parameter . "\n\n";
 | |
|             display_help();
 | |
|             exit;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* additional error check */
 | |
| if ($rrdfile == "") {
 | |
|     echo "FATAL: You must specify an RRDfile!\n\n";
 | |
|     display_help();
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| /* determine the temporary file name */
 | |
| $seed = mt_rand();
 | |
| if ($config["cacti_server_os"] == "win32") {
 | |
|     $tempdir  = getenv("TEMP");
 | |
|     $xmlfile = $tempdir . "/" . str_replace(".rrd", "", basename($rrdfile)) . ".dump." . $seed;
 | |
| } else {
 | |
|     $tempdir = "/tmp";
 | |
|     $xmlfile = "/tmp/" . str_replace(".rrd", "", basename($rrdfile)) . ".dump." . $seed;
 | |
| }
 | |
| 
 | |
| if ($html) {
 | |
|     echo "<table cellpadding='3' cellspacing='0' class='spikekill_data' id='spikekill_data'>";
 | |
| }
 | |
| 
 | |
| if ($using_cacti) {
 | |
|     cacti_log("NOTE: Removing Spikes for '$rrdfile', Method:'$method'", false, "WEBUI");
 | |
| }
 | |
| 
 | |
| /* execute the dump command */
 | |
| echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "NOTE: Creating XML file '$xmlfile' from '$rrdfile'" . ($html ? "</td></tr>\n":"\n");
 | |
| 
 | |
| if ($using_cacti) {
 | |
|     shell_exec(read_config_option("path_rrdtool") . " dump $rrdfile > $xmlfile");
 | |
| } 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) {
 | |
|         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");
 | |
|     } else {
 | |
|         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");
 | |
|     }
 | |
|     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.
 | |
| */
 | |
| $rra     = array();
 | |
| $rra_cf  = array();
 | |
| $rra_pdp = array();
 | |
| $rra_num = 0;
 | |
| $ds_num  = 0;
 | |
| $total_kills = 0;
 | |
| $in_rra  = false;
 | |
| $in_db   = false;
 | |
| $ds_min  = array();
 | |
| $ds_max  = array();
 | |
| $ds_name = array();
 | |
| 
 | |
| /* 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) {
 | |
|         if (substr_count($line, "<v>")) {
 | |
|             $linearray = explode("<v>", $line);
 | |
|             /* discard the row */
 | |
|             array_shift($linearray);
 | |
|             $ds_num = 0;
 | |
|             foreach ($linearray as $dsvalue) {
 | |
|                 /* peel off garbage */
 | |
|                 $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;
 | |
|                     } else {
 | |
|                         $rra[$rra_num][$ds_num]["numsamples"]++;
 | |
|                     }
 | |
| 
 | |
|                     if (!isset($rra[$rra_num][$ds_num]["sumofsamples"])) {
 | |
|                         $rra[$rra_num][$ds_num]["sumofsamples"] = $dsvalue;
 | |
|                     } else {
 | |
|                         $rra[$rra_num][$ds_num]["sumofsamples"] += $dsvalue;
 | |
|                     }
 | |
| 
 | |
|                     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;
 | |
|                     }
 | |
| 
 | |
|                     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;
 | |
|                     }
 | |
| 
 | |
|                     /* store the sample for standard deviation calculation */
 | |
|                     $samples[$rra_num][$ds_num][] = $dsvalue;
 | |
|                 }
 | |
| 
 | |
|                 if (!isset($rra[$rra_num][$ds_num]["totalsamples"])) {
 | |
|                     $rra[$rra_num][$ds_num]["totalsamples"] = 1;
 | |
|                 } else {
 | |
|                     $rra[$rra_num][$ds_num]["totalsamples"]++;
 | |
|                 }
 | |
| 
 | |
|                 $ds_num++;
 | |
|             }
 | |
|         } elseif (substr_count($line, "<rra>")) {
 | |
|             $in_rra = true;
 | |
|         } 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>")) {
 | |
|             $in_rra = false;
 | |
|             $rra_num++;
 | |
|         } elseif (substr_count($line, "<step>")) {
 | |
|             $step = trim(str_replace("<step>", "", str_replace("</step>", "", trim($line))));
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* 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
 | |
| */
 | |
| echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "NOTE: Searching for Spikes in XML file '$xmlfile'" . ($html ? "</td></tr>\n":"\n");
 | |
| 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) {
 | |
|         if (!$dryrun) {
 | |
|             $new_output = updateXML($output, $rra);
 | |
|         }
 | |
|     } else {
 | |
|         echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "NOTE: NO Standard Deviation Spikes found in '$rrdfile'" . ($html ? "</td></tr>\n":"\n");
 | |
|     }
 | |
| } else {
 | |
|     /* variance subroutine */
 | |
|     if ($var_kills) {
 | |
|         if (!$dryrun) {
 | |
|             $new_output = updateXML($output, $rra);
 | |
|         }
 | |
|     } else {
 | |
|         echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "NOTE: NO Variance Spikes found in '$rrdfile'" . ($html ? "</td></tr>\n":"\n");
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* finally update the file XML file and Reprocess the RRDfile */
 | |
| if (!$dryrun) {
 | |
|     if ($total_kills) {
 | |
|         if (writeXMLFile($new_output, $xmlfile)) {
 | |
|             if (backupRRDFile($rrdfile)) {
 | |
|                 createRRDFileFromXML($xmlfile, $rrdfile);
 | |
|             } else {
 | |
|                 echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "FATAL: Unable to backup '$rrdfile'" . ($html ? "</td></tr>\n":"\n");
 | |
|             }
 | |
|         } else {
 | |
|             echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "FATAL: Unable to write XML file '$xmlfile'" . ($html ? "</td></tr>\n":"\n");
 | |
|         }
 | |
|     }
 | |
| } else {
 | |
|     echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "NOTE: Dryrun requested.  No updates performed" . ($html ? "</td></tr>\n":"\n");
 | |
| }
 | |
| 
 | |
| if ($html) {
 | |
|     echo "</table>";
 | |
| }
 | |
| 
 | |
| /* All Functions */
 | |
| function createRRDFileFromXML($xmlfile, $rrdfile)
 | |
| {
 | |
|     global $using_cacti, $html;
 | |
| 
 | |
|     /* execute the dump command */
 | |
|     echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "NOTE: Re-Importing '$xmlfile' to '$rrdfile'" . ($html ? "</td></tr>\n":"\n");
 | |
|     if ($using_cacti) {
 | |
|         $response = shell_exec(read_config_option("path_rrdtool") . " restore -f -r $xmlfile $rrdfile");
 | |
|     } else {
 | |
|         $response = shell_exec("rrdtool restore -f -r $xmlfile $rrdfile");
 | |
|     }
 | |
|     if (strlen($response)) {
 | |
|         echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . $response . ($html ? "</td></tr>\n":"\n");
 | |
|     }
 | |
| }
 | |
| 
 | |
| function writeXMLFile($output, $xmlfile)
 | |
| {
 | |
|     return file_put_contents($xmlfile, $output);
 | |
| }
 | |
| 
 | |
| function backupRRDFile($rrdfile)
 | |
| {
 | |
|     global $using_cacti, $tempdir, $seed, $html;
 | |
| 
 | |
|     if ($using_cacti) {
 | |
|         $backupdir = read_config_option("spikekill_backupdir");
 | |
| 
 | |
|         if ($backupdir == "") {
 | |
|             $backupdir = $tempdir;
 | |
|         }
 | |
|     } else {
 | |
|         $backupdir = $tempdir;
 | |
|     }
 | |
| 
 | |
|     if (file_exists($backupdir . "/" . basename($rrdfile))) {
 | |
|         $newfile = basename($rrdfile) . "." . $seed;
 | |
|     } else {
 | |
|         $newfile = basename($rrdfile);
 | |
|     }
 | |
| 
 | |
|     echo ($html ? "<tr><td colspan='20' class='spikekill_note'>":"") . "NOTE: Backing Up '$rrdfile' to '" . $backupdir . "/" .  $newfile . "'" . ($html ? "</td></tr>\n":"\n");
 | |
| 
 | |
|     return copy($rrdfile, $backupdir . "/" . $newfile);
 | |
| }
 | |
| 
 | |
| 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) {
 | |
|                         $rra[$rra_num][$ds_num]["variance_avg"] = "NAN";
 | |
|                     } else {
 | |
|                         rsort($ds, SORT_NUMERIC);
 | |
|                         $ds = array_slice($ds, $outliers);
 | |
| 
 | |
|                         sort($ds, SORT_NUMERIC);
 | |
|                         $ds = array_slice($ds, $outliers);
 | |
| 
 | |
|                         $rra[$rra_num][$ds_num]["variance_avg"] = array_sum($ds) / sizeof($ds);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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])) {
 | |
|                         $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;
 | |
|                         }
 | |
|                         $rra[$rra_num][$ds_num]["average"]    = $rra[$rra_num][$ds_num]["sumofsamples"] / $rra[$rra_num][$ds_num]["numsamples"];
 | |
| 
 | |
|                         $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];
 | |
|                         }
 | |
| 
 | |
|                         $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];
 | |
|                         }
 | |
| 
 | |
|                         $rra[$rra_num][$ds_num]["numnksamples"] = 0;
 | |
|                         $rra[$rra_num][$ds_num]["sumnksamples"] = 0;
 | |
|                         $rra[$rra_num][$ds_num]["avgnksamples"] = 0;
 | |
| 
 | |
|                         /* go through values and find cutoffs */
 | |
|                         $rra[$rra_num][$ds_num]["stddev_killed"]    = 0;
 | |
|                         $rra[$rra_num][$ds_num]["variance_killed"]  = 0;
 | |
| 
 | |
|                         if (sizeof($samples[$rra_num][$ds_num])) {
 | |
|                             foreach ($samples[$rra_num][$ds_num] as $sample) {
 | |
|                                 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"]++;
 | |
|                                     $std_kills = true;
 | |
|                                 } else {
 | |
|                                     $rra[$rra_num][$ds_num]["numnksamples"]++;
 | |
|                                     $rra[$rra_num][$ds_num]["sumnksamples"] += $sample;
 | |
|                                 }
 | |
| 
 | |
|                                 if ($rra[$rra_num][$ds_num]["variance_avg"] == "NAN") {
 | |
|                                     /* not enought samples to calculate */
 | |
|                                 } elseif ($sample > ($rra[$rra_num][$ds_num]["variance_avg"] * (1+$percent))) {
 | |
|                                     /* kill based upon variance */
 | |
|                                     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"]++;
 | |
|                                     $var_kills = true;
 | |
|                                 }
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                         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"];
 | |
|                         }
 | |
|                     } else {
 | |
|                         $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";
 | |
|                     }
 | |
| 
 | |
|                         $ds_num++;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             $rra_num++;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| function outputStatistics($rra)
 | |
| {
 | |
|     global $rra_cf, $rra_name, $ds_name, $rra_pdp, $html;
 | |
| 
 | |
|     if (sizeof($rra)) {
 | |
|         if (!$html) {
 | |
|             echo "\n";
 | |
|             printf(
 | |
|                 "%10s %16s %10s %7s %7s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n",
 | |
|                 "Size",
 | |
|                 "DataSource",
 | |
|                 "CF",
 | |
|                 "Samples",
 | |
|                 "NonNan",
 | |
|                 "Avg",
 | |
|                 "StdDev",
 | |
|                 "MaxValue",
 | |
|                 "MinValue",
 | |
|                 "MaxStdDev",
 | |
|                 "MinStdDev",
 | |
|                 "StdKilled",
 | |
|                 "VarKilled",
 | |
|                 "StdDevAvg",
 | |
|                 "VarAvg"
 | |
|             );
 | |
|             printf(
 | |
|                 "%10s %16s %10s %7s %7s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n",
 | |
|                 "----------",
 | |
|                 "---------------",
 | |
|                 "----------",
 | |
|                 "-------",
 | |
|                 "-------",
 | |
|                 "----------",
 | |
|                 "----------",
 | |
|                 "----------",
 | |
|                 "----------",
 | |
|                 "----------",
 | |
|                 "----------",
 | |
|                 "----------",
 | |
|                 "----------",
 | |
|                 "----------",
 | |
|                 "----------"
 | |
|             );
 | |
|             foreach ($rra as $rra_key => $dses) {
 | |
|                 if (sizeof($dses)) {
 | |
|                     foreach ($dses as $dskey => $ds) {
 | |
|                         printf(
 | |
|                             "%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",
 | |
|                             displayTime($rra_pdp[$rra_key]),
 | |
|                             $ds_name[$dskey],
 | |
|                             $rra_cf[$rra_key],
 | |
|                             $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")
 | |
|                         );
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             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",
 | |
|                 "Size",
 | |
|                 "DataSource",
 | |
|                 "CF",
 | |
|                 "Samples",
 | |
|                 "NonNan",
 | |
|                 "Avg",
 | |
|                 "StdDev",
 | |
|                 "MaxValue",
 | |
|                 "MinValue",
 | |
|                 "MaxStdDev",
 | |
|                 "MinStdDev",
 | |
|                 "StdKilled",
 | |
|                 "VarKilled",
 | |
|                 "StdDevAvg",
 | |
|                 "VarAvg"
 | |
|             );
 | |
|             foreach ($rra as $rra_key => $dses) {
 | |
|                 if (sizeof($dses)) {
 | |
|                     foreach ($dses as $dskey => $ds) {
 | |
|                         printf(
 | |
|                             "<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",
 | |
|                             displayTime($rra_pdp[$rra_key]),
 | |
|                             $ds_name[$dskey],
 | |
|                             $rra_cf[$rra_key],
 | |
|                             $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")
 | |
|                         );
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| function updateXML(&$output, &$rra)
 | |
| {
 | |
|     global $numspike, $percent, $avgnan, $method, $total_kills;
 | |
|         $new_array = array();
 | |
| 
 | |
|     /* variance subroutine */
 | |
|     $rra_num = 0;
 | |
|     $ds_num  = 0;
 | |
|     $kills   = 0;
 | |
| 
 | |
|     if (sizeof($output)) {
 | |
|         foreach ($output as $line) {
 | |
|             if (substr_count($line, "<v>")) {
 | |
|                 $linearray = explode("<v>", $line);
 | |
|                 /* discard the row */
 | |
|                 array_shift($linearray);
 | |
| 
 | |
|                 /* initialize variables */
 | |
|                 $ds_num  = 0;
 | |
|                 $out_row = "<row>";
 | |
|                 foreach ($linearray as $dsvalue) {
 | |
|                     /* peel off garbage */
 | |
|                     $dsvalue = trim(str_replace("</row>", "", str_replace("</v>", "", $dsvalue)));
 | |
|                     if (strtolower($dsvalue) == "nan") {
 | |
|                         /* do nothing, it's a NaN */
 | |
|                     } else {
 | |
|                         if ($method == 2) {
 | |
|                             if ($dsvalue > (1+$percent)*$rra[$rra_num][$ds_num]["variance_avg"]) {
 | |
|                                 if ($kills < $numspike) {
 | |
|                                     if ($avgnan == "avg") {
 | |
|                                         $dsvalue = $rra[$rra_num][$ds_num]["variance_avg"];
 | |
|                                     } else {
 | |
|                                         $dsvalue = "NaN";
 | |
|                                     }
 | |
|                                     $kills++;
 | |
|                                     $total_kills++;
 | |
|                                 }
 | |
|                             }
 | |
|                         } else {
 | |
|                             if (($dsvalue > $rra[$rra_num][$ds_num]["max_cutoff"]) ||
 | |
|                             ($dsvalue < $rra[$rra_num][$ds_num]["min_cutoff"])) {
 | |
|                                 if ($kills < $numspike) {
 | |
|                                     if ($avgnan == "avg") {
 | |
|                                         $dsvalue = $rra[$rra_num][$ds_num]["average"];
 | |
|                                     } else {
 | |
|                                         $dsvalue = "NaN";
 | |
|                                     }
 | |
|                                     $kills++;
 | |
|                                     $total_kills++;
 | |
|                                 }
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     $out_row .= "<v> " . $dsvalue . "</v>";
 | |
|                     $ds_num++;
 | |
|                 }
 | |
| 
 | |
|                 $out_row .= "</row>";
 | |
| 
 | |
|                 $new_array[] = $out_row;
 | |
|             } else {
 | |
|                 if (substr_count($line, "</rra>")) {
 | |
|                     $ds_minmax = array();
 | |
|                     $rra_num++;
 | |
|                     $kills = 0;
 | |
|                 } elseif (substr_count($line, "</database>")) {
 | |
|                     $ds_num++;
 | |
|                     $kills = 0;
 | |
|                 }
 | |
| 
 | |
|                 $new_array[] = $line;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return $new_array;
 | |
| }
 | |
| 
 | |
| function removeComments(&$output)
 | |
| {
 | |
|     $new_array = array();
 | |
|     if (sizeof($output)) {
 | |
|         foreach ($output as $line) {
 | |
|             $line = trim($line);
 | |
|             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, "-->");
 | |
|                     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 ($line != "") {
 | |
|                     $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) {
 | |
|         return $total_time . " secs";
 | |
|     } else {
 | |
|         $total_time = $total_time / 60;
 | |
| 
 | |
|         if ($total_time < 60) {
 | |
|             return $total_time . " mins";
 | |
|         } else {
 | |
|             $total_time = $total_time / 60;
 | |
| 
 | |
|             if ($total_time < 24) {
 | |
|                 return $total_time . " hours";
 | |
|             } else {
 | |
|                 $total_time = $total_time / 24;
 | |
| 
 | |
|                 return $total_time . " days";
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| function debug($string)
 | |
| {
 | |
|     global $debug;
 | |
| 
 | |
|     if ($debug) {
 | |
|         echo "DEBUG: " . $string . "\n";
 | |
|     }
 | |
| }
 | |
| 
 | |
| function standard_deviation($samples)
 | |
| {
 | |
|     $sample_count = count($samples);
 | |
|         $sample_square = array();
 | |
| 
 | |
|     for ($current_sample = 0; $sample_count > $current_sample; ++$current_sample) {
 | |
|         $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 {
 | |
|         $version = "v1.0";
 | |
|     }
 | |
| 
 | |
|     echo "Cacti Spike Remover " . ($using_cacti ? "v" . $version["version"] : $version) . ", Copyright 2009, The Cacti Group, Inc.\n\n";
 | |
|     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";
 | |
| }
 |