username = 'OsUser:' . get_current_user(); $this->user_info = array(); if (isset($_SESSION['sess_user_id'])) { $this->user = $_SESSION['sess_user_id']; /* confirm the user id is accurate */ $this->user_info = db_fetch_row_prepared('SELECT id, username FROM user_auth WHERE id = ?', array($this->user)); if (cacti_sizeof($this->user_info)) { $this->username = 'CactiUser:' . $this->user_info['username']; } } if ($rrdfile != '') { $this->rrdfile = $rrdfile; } if ($method != '') { $this->method = $method; } if ($avgnan != '') { $this->avgnan = $avgnan; } if ($stddev != '') { $this->stddev = $stddev; } if ($out_start != '') { $this->out_start = $out_start; } if ($out_end != '') { $this->out_end = $out_end; } if ($outliers != '') { $this->outliers = $outliers; } if ($percent != '') { $this->percent = $percent; } if ($numspike != '') { $this->numspike = $numspike; } $this->dmethod = read_config_option('spikekill_method', true); $this->dnumspike = read_config_option('spikekill_number', true); $this->dstddev = read_config_option('spikekill_deviations', true); $this->dpercent = read_config_option('spikekill_percent', true); $this->doutliers = read_config_option('spikekill_outliers', true); $this->davgnan = read_config_option('spikekill_avgnan', true); return true; } public function __destruct() { return true; } private function set_error($string) { $this->errors[] = $string; } private function is_error_set() { return cacti_sizeof($this->errors); } public function get_errors() { $output = ''; if (cacti_sizeof($this->errors)) { foreach($this->errors as $error) { $output .= ($output != '' ? ($this->html ? '
':"\n"):'') . $error; } } return $output; } public function get_output($html = true) { return $this->strout; } private function initialize_spikekill() { /* additional error check */ if ($this->rrdfile == '') { $this->set_error("FATAL: You must specify an RRDfile!"); } if (!file_exists($this->rrdfile)) { $this->set_error("FATAL: File '$this->rrdfile' does not exist."); } elseif (!is_writable($this->rrdfile)) { $this->set_error("FATAL: File '$this->rrdfile' is not writeable by '" . get_execution_user() . "'."); } $umethod = read_user_setting('spikekill_method', $this->dmethod, true); $unumspike = read_user_setting('spikekill_number', $this->dnumspike, true); $ustddev = read_user_setting('spikekill_deviations', $this->dstddev, true); $upercent = read_user_setting('spikekill_percent', $this->dpercent, true); $uoutliers = read_user_setting('spikekill_outliers', $this->doutliers, true); $uavgnan = read_user_setting('spikekill_avgnan', $this->davgnan, true); /* set the correct value */ if ($this->avgnan == '') { if (!isset($uavgnan)) { $this->avgnan = $davgnan; } else { $this->avgnan = $uavgnan; } } if ($this->method == '') { if (!isset($umethod)) { $this->method = $dmethod; } else { $this->method = $umethod; } } if ($this->numspike == '') { if (!isset($unumspike)) { $this->numspike = $dnumspike; } else { $this->numspike = $unumspike; } } if ($this->stddev == '') { if (!isset($ustddev)) { $this->stddev = $dstddev; } else { $this->stddev = $ustddev; } } if ($this->percent == '') { if (!isset($upercent)) { $this->percent = $dpercent; } else { $this->percent = $upercent; } } if ($this->outliers == '') { if (!isset($uoutliers)) { $this->outliers = $doutliers; } else { $this->outliers = $uoutliers; } } if (!is_numeric($this->stddev) || ($this->stddev < 1)) { $this->set_error("FATAL: Standard Deviation must be a positive integer."); } if ($this->method == 'float' || $this->method == 'fill') { if (!is_numeric($this->out_start)) { $this->out_start = strtotime($this->out_start); } if (!is_numeric($this->out_end)) { $this->out_end = strtotime($this->out_end); } if ($this->out_start === false || $this->out_end === false) { $this->set_error("FATAL: The outlier-start and outlier-end arguments must be in the format of YYYY-MM-DD HH:MM."); } if (!is_numeric($this->outliers) || ($this->outliers < 1)) { $this->set_error("FATAL: The number of outliers to exclude must be a positive integer."); } $this->out_set = true; } if ($this->percent != '') { if (is_numeric($this->percent) && $this->percent > 0) { $this->percent = $this->percent/100; } else { $this->set_error("FATAL: Percent deviation must be a positive floating point number."); } } if (!$this->numspike != '') { if (!is_numeric($this->numspike) || ($this->numspike < 1)) { $this->set_error("FATAL: Number of spikes to remove must be a positive integer"); } } if ((!empty($this->out_start) && empty($this->out_end)) || (!empty($this->out_end) && empty($this->out_start))) { $this->set_error("FATAL: Outlier time range requires outlier-start and outlier-end to be specified."); } if (!empty($this->out_start)) { if ($this->out_start >= $this->out_end) { $this->set_error("FATAL: Outlier time range requires outlier-start to be less than outlier-end."); } } switch($this->method) { /* the order of the following case statements reflects the order in the spikekill menu in the GUI. */ case 'stddev': $this->method = SPIKE_METHOD_STDDEV; break; case 'variance': $this->method = SPIKE_METHOD_VARIANCE; break; case 'fill': $this->method = SPIKE_METHOD_FILL; break; case 'float': $this->method = SPIKE_METHOD_FLOAT; break; default: $this->set_error("FATAL: You must specify either 'stddev', 'variance', 'float', or 'fill' as methods."); } if ($this->method == SPIKE_METHOD_FLOAT && empty($this->out_start)) { $this->set_error("FATAL: The 'float' removal method requires the specification of a start and end date."); } if ($this->method == SPIKE_METHOD_FILL && empty($this->out_start)) { $this->set_error("FATAL: The 'gapfill' removal method requires the specification of a start and end date."); } switch($this->avgnan) { case 'avg': case 'last': case 'nan': break; default: $this->set_error("FATAL: You must specify either 'last', 'avg' or 'nan' as a replacement method."); } return false; } public function remove_spikes() { global $config; $this->initialize_spikekill(); if ($this->is_error_set()) { return false; } /* determine the temporary file name */ $this->seed = mt_rand(); if ($config['cacti_server_os'] == 'win32') { $this->tempdir = read_config_option('spikekill_backupdir'); $xmlfile = $this->tempdir . '/' . str_replace('.rrd', '', basename($this->rrdfile)) . '.dump.' . $this->seed; $bakfile = $this->tempdir . '/' . str_replace('.rrd', '', basename($this->rrdfile)) . '.backup.' . $this->seed . '.rrd'; } else { $this->tempdir = read_config_option('spikekill_backupdir'); $xmlfile = $this->tempdir . '/' . str_replace('.rrd', '', basename($this->rrdfile)) . '.dump.' . $this->seed; $bakfile = $this->tempdir . '/' . str_replace('.rrd', '', basename($this->rrdfile)) . '.backup.' . $this->seed . '.rrd'; } $this->strout = ''; if (!empty($this->out_start) && !$this->dryrun) { $this->strout .= ($this->html ? "

":'') . "NOTE: Removing Outliers in Range and Replacing with Last" . ($this->html ? "

\n":"\n"); } /* execute the dump command */ $this->strout .= ($this->html ? "

":'') . "NOTE: Creating XML file '$xmlfile' from '$this->rrdfile'" . ($this->html ? "

\n":"\n"); if (!$this->dryrun) { switch ($this->method) { case SPIKE_METHOD_STDDEV: $mm = 'StdDev'; $mes = "$this->username, File:" . basename($this->rrdfile) . ", Method:$mm, StdDevs:$this->stddev, AvgNan:$this->avgnan, Kills:$this->numspike, Outliers:$this->outliers"; break; case SPIKE_METHOD_VARIANCE: $mm = 'Variance'; $mes = "$this->username, File:" . basename($this->rrdfile) . ", Method:$mm, AvgNan:$this->avgnan, Kills:$this->numspike, Outliers:$this->outliers, Percent:" . round($this->percent*100,2) . "%"; break; case SPIKE_METHOD_FLOAT: $mm = 'RangeFloat'; $mes = "$this->username, File:" . basename($this->rrdfile) . ", Method:$mm, OutStart:$this->out_start, OutEnd:$this->out_end, AvgNan:$this->avgnan"; break; case SPIKE_METHOD_FILL: $mm = 'GapFill'; $mes = "$this->username, File:" . basename($this->rrdfile) . ", Method:$mm, OutStart:$this->out_start, OutEnd:$this->out_end, AvgNan:$this->avgnan"; break; default: $mm = 'Undefined'; $mes = "$this->username, File:" . basename($this->rrdfile) . ", Method:$mm"; } cacti_log($mes, false, 'SPIKEKILL'); } shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' dump ' . cacti_escapeshellarg($this->rrdfile) . ' > ' . cacti_escapeshellarg($xmlfile)); /* read the xml file into an array*/ if (file_exists($xmlfile)) { $output = file($xmlfile); /* remove the temp file */ unlink($xmlfile); } else { $this->set_error("FATAL: RRDtool Command Failed. Please verify that the RRDtool path is valid in Settings->Paths!"); return false; } /* backup the rrdfile if requested */ if ($this->backup && !$this->dryrun) { if (copy($this->rrdfile, $bakfile)) { $this->strout .= ($this->html ? "

":'') . "NOTE: RRDfile '$this->rrdfile' backed up to '$bakfile'" . ($this->html ? "

\n":"\n"); } else { $this->set_error("FATAL: RRDfile Backup of '$this->rrdfile' to '$bakfile' FAILED!"); return false; } } if ($this->is_error_set()) { return false; } /* process the xml file and remove all comments */ $output = $this->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][timestamp]; 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(); $this->rra_cf = array(); $this->rra_pdp = array(); $rra_num = 0; $ds_num = 0; $this->total_kills = 0; $in_rra = false; $in_db = false; $this->ds_min = array(); $this->ds_max = array(); $this->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 (cacti_sizeof($output)) { foreach($output as $line) { if (substr_count($line, '')) { $linearray = explode('', $line); /* get the timestamp */ $timestamp_part = $linearray[0]; if (strpos($timestamp_part, '') !== false) { $timestamp_part = str_replace('', '', $timestamp_part); $timestamp_part = str_replace('', '', $timestamp_part); $timestamp = trim($timestamp_part); } else { $timestamp = 0; } /* discard the first piece of the exploded line */ array_shift($linearray); $ds_num = 0; foreach($linearray as $dsvalue) { /* peel off garbage */ $dsvalue = trim(str_replace('', '', str_replace('', '', $dsvalue))); /* check for outlier territory */ if ($timestamp > 0) { if (!empty($this->out_start) && $timestamp < $this->out_start) { $process = true; } elseif (!empty($this->out_end) && $timestamp > $this->out_end) { $process = true; } elseif (empty($this->out_start)) { $process = true; } else { $process = false; } } else { $process = true; } if (strtolower($dsvalue) != 'nan' && $process) { 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; } elseif (is_numeric($dsvalue)) { $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 */ if ($timestamp == 0) { $samples[$rra_num][$ds_num][] = $dsvalue; } else { $samples[$rra_num][$ds_num][$timestamp] = $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, '')) { $in_rra = true; } elseif (substr_count($line, '')) { $this->ds_min[] = trim(str_replace('', '', str_replace('', '', trim($line)))); } elseif (substr_count($line, '')) { $this->ds_max[] = trim(str_replace('', '', str_replace('', '', trim($line)))); } elseif (substr_count($line, '')) { $this->ds_name[] = trim(str_replace('', '', str_replace('', '', trim($line)))); } elseif (substr_count($line, '')) { $this->rra_cf[] = trim(str_replace('', '', str_replace('', '', trim($line)))); } elseif (substr_count($line, '')) { $this->rra_pdp[] = trim(str_replace('', '', str_replace('', '', trim($line)))); } elseif (substr_count($line, '')) { $in_rra = false; $rra_num++; } elseif (substr_count($line, '')) { $this->step = trim(str_replace('', '', str_replace('', '', trim($line)))); } } } cacti_log("DEBUG: number of RRAs: {$rra_num}", false, 'SPIKE', POLLER_VERBOSITY_DEBUG); cacti_log("DEBUG: number of DSes: {$ds_num}", false, 'SPIKE', POLLER_VERBOSITY_DEBUG); /* For all the samples determine the average with the outliers removed */ $this->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 * */ if (empty($this->out_start)) { $this->strout .= ($this->html ? "

":'') . "NOTE: Searching for Spikes in XML file '$xmlfile'" . ($this->html ? "

\n":"\n"); } else { $this->strout .= ($this->html ? "

":'') . "NOTE: Limited to window: " . date('M j, Y H:i:s',$this->out_start) . " thru " . date('M j, Y H:i:s',$this->out_end) . ($this->html ? "

\n":"\n"); cacti_log("DEBUG: Limited to window: " . date('M j, Y H:i:s',$this->out_start) . " thru " . date('M j, Y H:i:s',$this->out_end), false, 'SPIKE', POLLER_VERBOSITY_DEBUG); } $this->calculateOverallStatistics($rra, $samples); /* debugging and/or status report */ if ($this->debug || $this->dryrun) { if ($this->html) { $this->strout .= ""; } $this->outputStatistics($rra); if ($this->html) { $this->strout .= '
'; } } $new_output = ''; /* create an output array */ if ($this->method == SPIKE_METHOD_STDDEV) { /* standard deviation subroutine */ if ($this->std_kills || $this->out_kills) { $this->debug('Either std_kills or out_kills found'); if (!$this->dryrun) { $new_output = $this->updateXML($output, $rra); $output = true; } } elseif (!empty($this->out_start)) { $this->strout .= ($this->html ? "

":'') . "NOTE: No Window Spikes found in '$this->rrdfile'" . ($this->html ? "

\n":"\n"); } else { $this->strout .= ($this->html ? "

":'') . "NOTE: No Standard Deviation Spikes found in '$this->rrdfile'" . ($this->html ? "

\n":"\n"); } } else { /* variance subroutine */ if ($this->var_kills || $this->out_kills) { $this->debug('Either variance or out_kills found'); if (!$this->dryrun) { $new_output = $this->updateXML($output, $rra); $output = true; } } elseif (!empty($this->out_start)) { $this->strout .= ($this->html ? "

":'') . "NOTE: No Window Fills found in '$this->rrdfile'" . ($this->html ? "

\n":"\n"); } else { $this->strout .= ($this->html ? "

":'') . "NOTE: No Variance Spikes found in '$this->rrdfile'" . ($this->html ? "

\n":"\n"); } } /* finally update the file XML file and Reprocess the RRDfile */ if (!$this->dryrun) { if ($output == true && $new_output != '') { if ($this->writeXMLFile($new_output, $xmlfile)) { if ($this->backupRRDFile($this->rrdfile)) { $this->createRRDFileFromXML($xmlfile, $this->rrdfile); $this->strout .= ($this->html ? "

":'') . "NOTE: Spikes Found and Remediated. Total Spikes ($this->total_kills)" . ($this->html ? "

\n":"\n"); } else { $this->strout .= ($this->html ? "

":'') . "FATAL: Unable to backup '$this->rrdfile'" . ($this->html ? "

\n":"\n"); } } else { $this->strout .= ($this->html ? "

":'') . "FATAL: Unable to write XML file '$xmlfile'" . ($this->html ? "

\n":"\n"); } } else { $this->strout .= ($this->html ? "

":'') . "NOTE: No Spikes Found." . ($this->html ? "

\n":"\n"); } } else { $this->strout .= ($this->html ? "

":'') . "NOTE: Dryrun requested. No updates performed" . ($this->html ? "

\n":"\n"); } $this->strout .= ($this->html ? "":''); if ($this->total_kills > 0) { cacti_log("WARNING: Removed '$this->total_kills' Spikes from '$this->rrdfile', Method:'$this->method'", false, 'WEBUI'); } elseif($this->debug) { cacti_log("NOTE: Removed '$this->total_kills' Spikes from '$this->rrdfile', Method:'$this->method'", false, 'WEBUI'); } if (file_exists($xmlfile)) { unlink($xmlfile); } if (file_exists($xmlfile)) { unlink($bakfile); } return true; } /* All Functions */ private function createRRDFileFromXML($xmlfile, $rrdfile) { /* execute the dump command */ $this->strout .= ($this->html ? "

":'') . "NOTE: Re-Importing '$xmlfile' to '$rrdfile'" . ($this->html ? "

\n":"\n"); $response = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' restore -f -r ' . cacti_escapeshellarg($xmlfile) . ' ' . cacti_escapeshellarg($rrdfile)); if ($response != '') { $this->strout .= ($this->html ? "

":'') . $response . ($this->html ? "

\n":"\n"); } } private function writeXMLFile($output, $xmlfile) { return file_put_contents($xmlfile, $output); } private function backupRRDFile($rrdfile) { $backupdir = read_config_option('spikekill_backupdir'); if ($backupdir == '') { $backupdir = $this->tempdir; } if (file_exists($backupdir . '/' . basename($rrdfile))) { $newfile = basename($rrdfile) . '.' . $this->seed; } else { $newfile = basename($rrdfile); } $this->strout .= ($this->html ? "

":'') . "NOTE: Backing Up '$rrdfile' to '" . $backupdir . '/' . $newfile . "'" . ($this->html ? "

\n":"\n"); return copy($rrdfile, $backupdir . "/" . $newfile); } private function calculateVarianceAverages(&$rra, &$samples) { if (cacti_sizeof($samples)) { foreach($samples as $rra_num => $dses) { if (cacti_sizeof($dses)) { foreach($dses as $ds_num => $ds) { if (empty($this->out_start)) { if (cacti_sizeof($ds) < $this->outliers * 3) { $rra[$rra_num][$ds_num]['variance_avg'] = 'NAN'; } else { $myds = $ds; /* remove NaN entries from the data set */ if (cacti_sizeof($myds)) { foreach($myds as $timestamp => $value) { if (stripos($value, 'nan') !== false) { unset($myds[$timestamp]); } } } /* remove high outliers */ rsort($myds, SORT_NUMERIC); $myds = array_slice($myds, $this->outliers); /* remove low outliers */ sort($myds, SORT_NUMERIC); $myds = array_slice($myds, $this->outliers); if (cacti_sizeof($myds)) { $rra[$rra_num][$ds_num]['variance_avg'] = array_sum($myds) / cacti_sizeof($myds); } else { $rra[$rra_num][$ds_num]['variance_avg'] = 'NAN'; } } } else { if (isset($rra[$rra_num][$ds_num]['sumofsamples']) && isset($rra[$rra_num][$ds_num]['numsamples'])) { if ($rra[$rra_num][$ds_num]['numsamples'] > 0) { $rra[$rra_num][$ds_num]['variance_avg'] = $rra[$rra_num][$ds_num]['sumofsamples'] / $rra[$rra_num][$ds_num]['numsamples']; } else { $rra[$rra_num][$ds_num]['variance_avg'] = 0; } } else { $rra[$rra_num][$ds_num]['variance_avg'] = 0; } } } } } } } private function calculateOverallStatistics(&$rra, &$samples) { $rra_num = 0; if (cacti_sizeof($rra)) { foreach($rra as $dses) { $ds_num = 0; if (cacti_sizeof($dses)) { foreach($dses as $ds) { if (isset($samples[$rra_num][$ds_num])) { $rra[$rra_num][$ds_num]['standard_deviation'] = $this->processStandardDeviationCalculation($samples[$rra_num][$ds_num]); if ($rra[$rra_num][$ds_num]['standard_deviation'] == 'NAN') { $rra[$rra_num][$ds_num]['standard_deviation'] = 0; } if (isset($rra[$rra_num][$ds_num]['sumofsamples']) && isset($rra[$rra_num][$ds_num]['numsamples'])) { if ($rra[$rra_num][$ds_num]['numsamples'] > 0) { $rra[$rra_num][$ds_num]['average'] = $rra[$rra_num][$ds_num]['sumofsamples'] / $rra[$rra_num][$ds_num]['numsamples']; } else { $rra[$rra_num][$ds_num]['average'] = 0; } } else { $rra[$rra_num][$ds_num]['average'] = 0; } $rra[$rra_num][$ds_num]['min_cutoff'] = $rra[$rra_num][$ds_num]['average'] - ($this->stddev * $rra[$rra_num][$ds_num]['standard_deviation']); if ($rra[$rra_num][$ds_num]['min_cutoff'] < $this->ds_min[$ds_num]) { $rra[$rra_num][$ds_num]['min_cutoff'] = $this->ds_min[$ds_num]; } $rra[$rra_num][$ds_num]['max_cutoff'] = $rra[$rra_num][$ds_num]['average'] + ($this->stddev * $rra[$rra_num][$ds_num]['standard_deviation']); if ($rra[$rra_num][$ds_num]['max_cutoff'] > $this->ds_max[$ds_num]) { $rra[$rra_num][$ds_num]['max_cutoff'] = $this->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; $rra[$rra_num][$ds_num]['outwind_killed'] = 0; /* count the number of kills required */ if (cacti_sizeof($samples[$rra_num][$ds_num])) { foreach($samples[$rra_num][$ds_num] as $timestamp => $sample) { if (!empty($this->out_start) && $timestamp >= $this->out_start && $timestamp <= $this->out_end) { if ($this->method == SPIKE_METHOD_FLOAT) { $this->debug(sprintf("Window Float Kill: Value '%.4e', Time '%s'", $sample, date('Y-m-d H:i', $timestamp))); $rra[$rra_num][$ds_num]['outwind_killed']++; $this->out_kills = true; } elseif ($this->method == SPIKE_METHOD_FILL) { if ($sample > (1+$this->percent)*$rra[$rra_num][$ds_num]['variance_avg'] || strtolower($sample) == 'nan') { $this->debug(sprintf("Window GapFill Kill: Value '%.4e', Time '%s'", $sample, date('Y-m-d H:i', $timestamp))); $rra[$rra_num][$ds_num]['outwind_killed']++; $this->out_kills = true; } } } elseif (($sample > $rra[$rra_num][$ds_num]['max_cutoff']) || ($sample < $rra[$rra_num][$ds_num]['min_cutoff'])) { if ($this->method == SPIKE_METHOD_STDDEV) { $this->debug(sprintf("StdDev Kill: Value '%.4e', StandardDev '%.4e', StdDevLimit '%.4e'", $sample, $rra[$rra_num][$ds_num]['standard_deviation'], ($rra[$rra_num][$ds_num]['max_cutoff'] * (1+$this->percent)))); $rra[$rra_num][$ds_num]['stddev_killed']++; $this->std_kills = true; } } elseif (is_numeric($sample)) { $rra[$rra_num][$ds_num]['numnksamples']++; $rra[$rra_num][$ds_num]['sumnksamples'] += $sample; } if (!empty($this->out_start) && $timestamp >= $this->out_start && $timestamp <= $this->out_end) { /* Already calculated */ } elseif ($rra[$rra_num][$ds_num]['variance_avg'] == 'NAN') { /* not enough samples to calculate */ } elseif ($sample > ($rra[$rra_num][$ds_num]['variance_avg'] * (1+$this->percent))) { if ($this->method == SPIKE_METHOD_VARIANCE) { /* kill based upon variance */ $this->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+$this->percent)))); $rra[$rra_num][$ds_num]['variance_killed']++; $this->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]['outwind_killed'] = 'N/A'; } $ds_num++; } } $rra_num++; } } } private function outputStatistics($rra) { if (cacti_sizeof($rra)) { if (!$this->html) { $this->strout .= "\n"; $this->strout .= sprintf("%10s %16s %10s %7s %7s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n", 'Size', 'DataSource', 'CF', 'Samples', 'NonNan', 'Avg', 'StdDev', 'MaxValue', 'MinValue', 'MaxStdDev', 'MinStdDev', 'StdKilled', 'VarKilled', 'WindFilled', 'StdDevAvg', 'VarAvg'); $this->strout .= sprintf("%10s %16s %10s %7s %7s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n", '----------', '---------------', '----------', '-------', '-------', '----------', '----------', '----------', '----------', '----------', '----------', '----------', '----------', '----------', '----------', '----------', '----------'); foreach($rra as $rra_key => $dses) { if (cacti_sizeof($dses)) { foreach($dses as $dskey => $ds) { $this->strout .= sprintf('%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 %10s ' . (isset($ds['avgnksamples']) ? ($ds['avgnksamples'] < 1E6 ? '%10s ':'%10.4e ') : '%10.4E ') . (isset($ds['variance_avg']) ? ($ds['variance_avg'] < 1E6 ? '%10s ':'%10.4e ') : '%10.4E ') . "\n", $this->displayTime($this->rra_pdp[$rra_key]), $this->ds_name[$dskey], $this->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['outwind_killed'], ($ds['avgnksamples'] != 'N/A' ? round($ds['avgnksamples'],2) : $ds['avgnksamples']), (isset($ds['variance_avg']) ? round($ds['variance_avg'],2) : 'N/A')); } } } $this->strout .= "\n"; } else { $this->strout .= sprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", 'Size', 'DataSource', 'CF', 'Samples', 'NonNan', 'Avg', 'StdDev', 'MaxValue', 'MinValue', 'MaxStdDev', 'MinStdDev', 'StdKilled', 'VarKilled', 'WindFilled', 'StdDevAvg', 'VarAvg'); foreach($rra as $rra_key => $dses) { if (cacti_sizeof($dses)) { foreach($dses as $dskey => $ds) { $this->strout .= sprintf('%s%s%s%s%s' . ($ds['average'] < 1E6 ? '%s':'%.4e') . ($ds['standard_deviation'] < 1E6 ? '%s':'%.4e') . (isset($ds['max_value']) ? ($ds['max_value'] < 1E6 ? '%s':'%.4e') : '%s') . (isset($ds['min_value']) ? ($ds['min_value'] < 1E6 ? '%s':'%.4e') : '%s') . (isset($ds['max_cutoff']) ? ($ds['max_cutoff'] < 1E6 ? '%s':'%.4e') : '%s') . (isset($ds['min_cutoff']) ? ($ds['min_cutoff'] < 1E6 ? '%s':'%.4e') : '%s') . '%s%s%s' . (isset($ds['avgnksampled']) ? ($ds['avgnksamples'] < 1E6 ? '%s':'%.4e') : '%s') . (isset($ds['variance_avg']) ? ($ds['variance_avg'] < 1E6 ? "%s":"%.4e") : "%s") . "\n\n", $this->displayTime($this->rra_pdp[$rra_key]), $this->ds_name[$dskey], $this->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['outwind_killed'], ($ds['avgnksamples'] != 'N/A' ? round($ds['avgnksamples'],2) : $ds['avgnksamples']), (isset($ds['variance_avg']) ? round($ds['variance_avg'],2) : 'N/A')); } } } } } } private function updateXML(&$output, &$rra) { /* variance subroutine */ $rra_num = 0; $ds_num = 0; $last_num = array(); $new_array = array(); if (cacti_sizeof($output)) { foreach($output as $line) { if (substr_count($line, '')) { $linearray = explode('', $line); /* get the timestamp */ $timestamp_part = $linearray[0]; if (strpos($timestamp_part, '') !== false) { $timestamp_part = str_replace('', '', $timestamp_part); $timestamp_part = str_replace('', '', $timestamp_part); $timestamp = trim($timestamp_part); } else { $timestamp = 0; } /* discard the first piece of the exploded line */ array_shift($linearray); /* initialize variables */ $ds_num = 0; $out_row = ''; $kills = 0; foreach($linearray as $dsvalue) { /* peel off garbage */ $dsvalue = trim(str_replace('', '', str_replace('', '', $dsvalue))); if (strtolower($dsvalue) == 'nan' && !isset($last_num[$ds_num])) { /* do nothing, it's a NaN, and the first one */ } elseif (!empty($this->out_start) && $timestamp > $this->out_start && $timestamp < $this->out_end) { /* a window is specified, and the timestamp is inside the window */ if ($this->method == SPIKE_METHOD_FLOAT) { if ($this->avgnan == 'avg') { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with variance_avg {$rra[$rra_num][$ds_num]['variance_avg']}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = sprintf('%1.10e', $rra[$rra_num][$ds_num]['variance_avg']); $kills++; $this->total_kills++; } elseif ($this->avgnan == 'last' && isset($last_num[$ds_num])) { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with last value {$last_num[$ds_num]}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = $last_num[$ds_num]; $kills++; $this->total_kills++; } } elseif ($this->method == SPIKE_METHOD_FILL) { if ($dsvalue > (1+$this->percent)*$rra[$rra_num][$ds_num]['variance_avg'] || strtolower($dsvalue) == 'nan') { if ($this->avgnan == 'avg') { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with variance_avg {$rra[$rra_num][$ds_num]['variance_avg']}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = sprintf('%1.10e', $rra[$rra_num][$ds_num]['variance_avg']); $kills++; $this->total_kills++; } elseif ($this->avgnan == 'last' && isset($last_num[$ds_num])) { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with last value {$last_num[$ds_num]}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = $last_num[$ds_num]; $kills++; $this->total_kills++; } } } elseif ($this->method == SPIKE_METHOD_VARIANCE) { if ($kills < $this->numspike) { if ($this->avgnan == 'avg') { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with variance_avg {$rra[$rra_num][$ds_num]['variance_avg']}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = sprintf('%1.10e', $rra[$rra_num][$ds_num]['variance_avg']); $this->total_kills++; $kills++; } elseif ($this->avgnan == 'last' && isset($last_num[$ds_num])) { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with last value {$last_num[$ds_num]}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = $last_num[$ds_num]; $this->total_kills++; $kills++; } else { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with NaN", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = 'NaN'; } } } elseif ($this->method == SPIKE_METHOD_STDDEV) { if ($kills < $this->numspike) { if ($this->avgnan == 'avg') { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with average {$rra[$rra_num][$ds_num]['average']}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = sprintf('%1.10e', $rra[$rra_num][$ds_num]['average']); $this->total_kills++; $kills++; } elseif ($this->avgnan == 'last' && isset($last_num[$ds_num])) { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with last value {$last_num[$ds_num]}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = $last_num[$ds_num]; $this->total_kills++; $kills++; } else { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with NaN", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = 'NaN'; } } } } elseif(strtolower($dsvalue) == 'nan' && isset($last_num[$ds_num])) { /** * We need to ignore this case as it's only a gap file when * There is a time range */ if ($this->method == SPIKE_METHOD_VARIANCE) { cacti_log("DEBUG: ignoring dsvalue {$dsvalue} as NaN values are left along when using the Variance Method!", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); } elseif ($this->method == SPIKE_METHOD_STDDEV) { cacti_log("DEBUG: ignoring dsvalue {$dsvalue} as NaN values are left alone when using the Standard Deviation Method!", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); } else { cacti_log("DEBUG: ignoring dsvalue {$dsvalue} as we are outside of the time range!", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); } } else { if ($this->method == SPIKE_METHOD_VARIANCE) { if ($dsvalue > (1+$this->percent)*$rra[$rra_num][$ds_num]['variance_avg']) { if ($kills < $this->numspike) { if ($this->avgnan == 'avg') { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with variance_avg {$rra[$rra_num][$ds_num]['variance_avg']}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = sprintf('%1.10e', $rra[$rra_num][$ds_num]['variance_avg']); $kills++; $this->total_kills++; } elseif ($this->avgnan == 'last' && isset($last_num[$ds_num])) { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with last value {$last_num[$ds_num]}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = $last_num[$ds_num]; $kills++; $this->total_kills++; } else { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with NaN", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = 'NaN'; } } } else { $last_num[$ds_num] = $dsvalue; } } else { if (($dsvalue > $rra[$rra_num][$ds_num]['max_cutoff']) || ($dsvalue < $rra[$rra_num][$ds_num]['min_cutoff'])) { if ($kills < $this->numspike) { if ($this->avgnan == 'avg') { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with average {$rra[$rra_num][$ds_num]['average']}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = sprintf('%1.10e', $rra[$rra_num][$ds_num]['average']); $kills++; $this->total_kills++; } elseif ($this->avgnan == 'last' && isset($last_num[$ds_num])) { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with last value {$last_num[$ds_num]}", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = $last_num[$ds_num]; $kills++; $this->total_kills++; } else { cacti_log("DEBUG: replacing dsvalue {$dsvalue} with NaN", false, 'SPIKEKILL', POLLER_VERBOSITY_DEBUG); $dsvalue = 'NaN'; } } } else { $last_num[$ds_num] = $dsvalue; } } } $out_row .= ' ' . $dsvalue . ''; $ds_num++; } $out_row .= "\n"; $new_array[] = $out_row; } else { if (substr_count($line, '')) { $ds_minmax = array(); $rra_num++; $kills = 0; $last_num = array(); } elseif (substr_count($line, '')) { $ds_num++; $kills = 0; $last_num = array(); } $new_array[] = $line; } } } return $new_array; } private function removeComments(&$output) { if (cacti_sizeof($output)) { foreach($output as $line) { $line = trim($line); if ($line == '') { continue; } else { /* is there a comment, remove it */ $oline = $line; $comment_start = 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 (strpos($line, '') !== false) { /* capture the timestamp */ $stamp = trim(substr($oline, $comment_start+4, $comment_end-4)); $stamp = explode('/', $stamp); $timestamp = trim($stamp[1]); $line = str_replace('', " $timestamp ", $line); } } if ($line != '') { $new_array[] = $line; } } } /* transfer the new array back to the original array */ return $new_array; } } private function displayTime($pdp) { $total_time = $pdp * $this->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'; } } } } private function debug($string) { if ($this->debug) { print 'DEBUG: ' . $string . "\n"; } } private function processStandardDeviationCalculation($samples) { $my_samples = $samples; if (!empty($this->out_start)) { foreach($samples as $timestamp => $value) { if ($timestamp < $this->out_start || $timestamp > $this->out_end) { $my_samples[] = $value; } } } return $this->calculateStandardDeviation($my_samples); } private function calculateStandardDeviation($items) { if (!function_exists('stats_standard_deviation')) { function stats_standard_deviation($items, $sample = false) { $total_items = cacti_count($items); if ($total_items === 0) { return false; } if ($sample && $total_items === 1) { return false; } $mean = array_sum($items) / $total_items; $carry = 0.0; foreach ($items as $val) { $d = ((double) $val) - $mean; $carry += $d * $d; } if ($sample) { --$total_items; } return sqrt($carry / $total_items); } } return stats_standard_deviation($items, false); } }