1#!/usr/local/bin/php -q 2<?php 3/* 4 ex: set tabstop=4 shiftwidth=4 autoindent: 5 +-------------------------------------------------------------------------+ 6 | Copyright (C) 2004-2009 The Cacti Group | 7 | | 8 | This program is free software; you can redistribute it and/or | 9 | modify it under the terms of the GNU General Public License | 10 | as published by the Free Software Foundation; either version 2 | 11 | of the License, or (at your option) any later version. | 12 | | 13 | This program is distributed in the hope that it will be useful, | 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 | GNU General Public License for more details. | 17 +-------------------------------------------------------------------------+ 18 | Cacti: The Complete RRDTool-based Graphing Solution | 19 +-------------------------------------------------------------------------+ 20 | This code is designed, written, and maintained by the Cacti Group. See | 21 | about.php and/or the AUTHORS file for specific developer information. | 22 +-------------------------------------------------------------------------+ 23 | http://www.cacti.net/ | 24 +-------------------------------------------------------------------------+ 25*/ 26 27/* do NOT run this script through a web browser */ 28if (! isset($_SERVER['argv'][0]) || isset($_SERVER['REQUEST_METHOD']) || isset($_SERVER['REMOTE_ADDR'])) { 29 exit('<br><strong>This script is only meant to run at the command line.</strong>'); 30} 31 32/* We are not talking to the browser */ 33$no_http_headers = true; 34 35$dir = dirname(__FILE__); 36chdir($dir); 37 38if (strpos($dir, 'spikekill') !== false) { 39 chdir('../../'); 40} 41 42$using_cacti = false; 43 44/* setup defaults */ 45$debug = false; 46$dryrun = false; 47$avgnan = 'avg'; 48$rrdfile = ''; 49$std_kills = true; 50$var_kills = true; 51$html = false; 52 53if ($using_cacti) { 54 $method = read_config_option('spikekill_method'); 55 $numspike = read_config_option('spikekill_number'); 56 $stddev = read_config_option('spikekill_deviations'); 57 $percent = read_config_option('spikekill_percent'); 58 $outliers = read_config_option('spikekill_outliers'); 59} else { 60 $method = 1; // Standard Deviation 61 $numspike = 10; 62 $stddev = 10; 63 $percent = 500; 64 $outliers = 5; 65} 66 67/* process calling arguments */ 68$parms = $_SERVER['argv']; 69array_shift($parms); 70 71foreach ($parms as $parameter) { 72 @[$arg, $value] = @explode('=', $parameter); 73 74 switch ($arg) { 75 case '--method': 76 case '-M': 77 if ($value == 'variance') { 78 $method = 2; 79 } elseif ($value == 'stddev') { 80 $method = 1; 81 } else { 82 echo "FATAL: You must specify either 'stddev' or 'variance' as methods.\n\n"; 83 display_help(); 84 exit; 85 } 86 87 break; 88 case '--avgnan': 89 case '-A': 90 if ($value == 'avg') { 91 $avgnan = 'avg'; 92 } elseif ($value == 'nan') { 93 $avgnan = 'nan'; 94 } else { 95 echo "FATAL: You must specify either 'avg' or 'nan' as replacement methods.\n\n"; 96 display_help(); 97 exit; 98 } 99 100 break; 101 case '--rrdfile': 102 case '-R': 103 $rrdfile = $value; 104 105 if (! file_exists($rrdfile)) { 106 echo "FATAL: File '$rrdfile' does not exist.\n"; 107 exit; 108 } 109 110 if (! is_writable($rrdfile)) { 111 echo "FATAL: File '$rrdfile' is not writable by this account.\n"; 112 exit; 113 } 114 115 break; 116 case '--stddev': 117 case '-S': 118 $stddev = $value; 119 120 if (! is_numeric($stddev) || ($stddev < 1)) { 121 echo "FATAL: Standard Deviation must be a positive integer.\n\n"; 122 display_help(); 123 exit; 124 } 125 126 break; 127 case '--outliers': 128 case '-O': 129 $outliers = $value; 130 131 if (! is_numeric($outliers) || ($outliers < 1)) { 132 echo "FATAL: The number of outliers to exlude must be a positive integer.\n\n"; 133 display_help(); 134 exit; 135 } 136 137 break; 138 case '--percent': 139 case '-P': 140 $percent = $value / 100; 141 142 if (! is_numeric($percent) || ($percent <= 0)) { 143 echo "FATAL: Percent deviation must be a positive floating point number.\n\n"; 144 display_help(); 145 exit; 146 } 147 148 break; 149 case '--html': 150 $html = true; 151 152 break; 153 case '-d': 154 case '--debug': 155 $debug = true; 156 157 break; 158 case '-D': 159 case '--dryrun': 160 $dryrun = true; 161 162 break; 163 case '--number': 164 case '-n': 165 $numspike = $value; 166 167 if (! is_numeric($numspike) || ($numspike < 1)) { 168 echo "FATAL: Number of spikes to remove must be a positive integer\n\n"; 169 display_help(); 170 exit; 171 } 172 173 break; 174 case '-h': 175 case '-v': 176 case '-V': 177 case '--version': 178 case '--help': 179 display_help(); 180 exit; 181 default: 182 print 'ERROR: Invalid Parameter ' . $parameter . "\n\n"; 183 display_help(); 184 exit; 185 } 186} 187 188/* additional error check */ 189if ($rrdfile == '') { 190 echo "FATAL: You must specify an RRDfile!\n\n"; 191 display_help(); 192 exit; 193} 194 195/* determine the temporary file name */ 196$seed = mt_rand(); 197if ($config['cacti_server_os'] == 'win32') { 198 $tempdir = getenv('TEMP'); 199 $xmlfile = $tempdir . '/' . str_replace('.rrd', '', basename($rrdfile)) . '.dump.' . $seed; 200} else { 201 $tempdir = '/tmp'; 202 $xmlfile = '/tmp/' . str_replace('.rrd', '', basename($rrdfile)) . '.dump.' . $seed; 203} 204 205if ($html) { 206 echo "<table cellpadding='3' cellspacing='0' class='spikekill_data' id='spikekill_data'>"; 207} 208 209if ($using_cacti) { 210 cacti_log("NOTE: Removing Spikes for '$rrdfile', Method:'$method'", false, 'WEBUI'); 211} 212 213/* execute the dump command */ 214echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . "NOTE: Creating XML file '$xmlfile' from '$rrdfile'" . ($html ? "</td></tr>\n" : "\n"); 215 216if ($using_cacti) { 217 shell_exec(read_config_option('path_rrdtool') . " dump $rrdfile > $xmlfile"); 218} else { 219 shell_exec("rrdtool dump $rrdfile > $xmlfile"); 220} 221 222/* read the xml file into an array*/ 223if (file_exists($xmlfile)) { 224 $output = file($xmlfile); 225 226 /* remove the temp file */ 227 unlink($xmlfile); 228} else { 229 if ($using_cacti) { 230 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"); 231 } else { 232 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"); 233 } 234 exit; 235} 236 237/* process the xml file and remove all comments */ 238$output = removeComments($output); 239 240/* Read all the rra's ds values and obtain the following pieces of information from each 241 rra archive. 242 243 * numsamples - The number of 'valid' non-nan samples 244 * sumofsamples - The sum of all 'valid' samples. 245 * average - The average of all samples 246 * standard_deviation - The standard deviation of all samples 247 * max_value - The maximum value of all samples 248 * min_value - The minimum value of all samples 249 * max_cutoff - Any value above this value will be set to the average. 250 * min_cutoff - Any value lower than this value will be set to the average. 251 252 This will end up being a n-dimensional array as follows: 253 rra[x][ds#]['totalsamples']; 254 rra[x][ds#]['numsamples']; 255 rra[x][ds#]['sumofsamples']; 256 rra[x][ds#]['average']; 257 rra[x][ds#]['stddev']; 258 rra[x][ds#]['max_value']; 259 rra[x][ds#]['min_value']; 260 rra[x][ds#]['max_cutoff']; 261 rra[x][ds#]['min_cutoff']; 262 263 There will also be a secondary array created with the actual samples. This 264 array will be used to calculate the standard deviation of the sample set. 265 samples[rra_num][ds_num][]; 266 267 Also track the min and max value for each ds and store it into the two 268 arrays: ds_min[ds#], ds_max[ds#]. 269 270 The we don't need to know the type of rra, only it's number for this analysis 271 the same applies for the ds' as well. 272*/ 273$rra = []; 274$rra_cf = []; 275$rra_pdp = []; 276$rra_num = 0; 277$ds_num = 0; 278$total_kills = 0; 279$in_rra = false; 280$in_db = false; 281$ds_min = []; 282$ds_max = []; 283$ds_name = []; 284 285/* perform a first pass on the array and do the following: 286 1) Get the number of good samples per ds 287 2) Get the sum of the samples per ds 288 3) Get the max and min values for all samples 289 4) Build both the rra and sample arrays 290 5) Get each ds' min and max values 291*/ 292if (sizeof($output)) { 293 foreach ($output as $line) { 294 if (substr_count($line, '<v>')) { 295 $linearray = explode('<v>', $line); 296 /* discard the row */ 297 array_shift($linearray); 298 $ds_num = 0; 299 foreach ($linearray as $dsvalue) { 300 /* peel off garbage */ 301 $dsvalue = trim(str_replace('</row>', '', str_replace('</v>', '', $dsvalue))); 302 if (strtolower($dsvalue) != 'nan') { 303 if (! isset($rra[$rra_num][$ds_num]['numsamples'])) { 304 $rra[$rra_num][$ds_num]['numsamples'] = 1; 305 } else { 306 $rra[$rra_num][$ds_num]['numsamples']++; 307 } 308 309 if (! isset($rra[$rra_num][$ds_num]['sumofsamples'])) { 310 $rra[$rra_num][$ds_num]['sumofsamples'] = $dsvalue; 311 } else { 312 $rra[$rra_num][$ds_num]['sumofsamples'] += $dsvalue; 313 } 314 315 if (! isset($rra[$rra_num][$ds_num]['max_value'])) { 316 $rra[$rra_num][$ds_num]['max_value'] = $dsvalue; 317 } elseif ($dsvalue > $rra[$rra_num][$ds_num]['max_value']) { 318 $rra[$rra_num][$ds_num]['max_value'] = $dsvalue; 319 } 320 321 if (! isset($rra[$rra_num][$ds_num]['min_value'])) { 322 $rra[$rra_num][$ds_num]['min_value'] = $dsvalue; 323 } elseif ($dsvalue < $rra[$rra_num][$ds_num]['min_value']) { 324 $rra[$rra_num][$ds_num]['min_value'] = $dsvalue; 325 } 326 327 /* store the sample for standard deviation calculation */ 328 $samples[$rra_num][$ds_num][] = $dsvalue; 329 } 330 331 if (! isset($rra[$rra_num][$ds_num]['totalsamples'])) { 332 $rra[$rra_num][$ds_num]['totalsamples'] = 1; 333 } else { 334 $rra[$rra_num][$ds_num]['totalsamples']++; 335 } 336 337 $ds_num++; 338 } 339 } elseif (substr_count($line, '<rra>')) { 340 $in_rra = true; 341 } elseif (substr_count($line, '<min>')) { 342 $ds_min[] = trim(str_replace('<min>', '', str_replace('</min>', '', trim($line)))); 343 } elseif (substr_count($line, '<max>')) { 344 $ds_max[] = trim(str_replace('<max>', '', str_replace('</max>', '', trim($line)))); 345 } elseif (substr_count($line, '<name>')) { 346 $ds_name[] = trim(str_replace('<name>', '', str_replace('</name>', '', trim($line)))); 347 } elseif (substr_count($line, '<cf>')) { 348 $rra_cf[] = trim(str_replace('<cf>', '', str_replace('</cf>', '', trim($line)))); 349 } elseif (substr_count($line, '<pdp_per_row>')) { 350 $rra_pdp[] = trim(str_replace('<pdp_per_row>', '', str_replace('</pdp_per_row>', '', trim($line)))); 351 } elseif (substr_count($line, '</rra>')) { 352 $in_rra = false; 353 $rra_num++; 354 } elseif (substr_count($line, '<step>')) { 355 $step = trim(str_replace('<step>', '', str_replace('</step>', '', trim($line)))); 356 } 357 } 358} 359 360/* For all the samples determine the average with the outliers removed */ 361calculateVarianceAverages($rra, $samples); 362 363/* Now scan the rra array and the samples array and calculate the following 364 1) The standard deviation of all samples 365 2) The average of all samples per ds 366 3) The max and min cutoffs of all samples 367 4) The number of kills in each ds based upon the thresholds 368*/ 369echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . "NOTE: Searching for Spikes in XML file '$xmlfile'" . ($html ? "</td></tr>\n" : "\n"); 370calculateOverallStatistics($rra, $samples); 371 372/* debugging and/or status report */ 373if ($debug || $dryrun) { 374 outputStatistics($rra); 375} 376 377/* create an output array */ 378if ($method == 1) { 379 /* standard deviation subroutine */ 380 if ($std_kills) { 381 if (! $dryrun) { 382 $new_output = updateXML($output, $rra); 383 } 384 } else { 385 echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . "NOTE: NO Standard Deviation Spikes found in '$rrdfile'" . ($html ? "</td></tr>\n" : "\n"); 386 } 387} else { 388 /* variance subroutine */ 389 if ($var_kills) { 390 if (! $dryrun) { 391 $new_output = updateXML($output, $rra); 392 } 393 } else { 394 echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . "NOTE: NO Variance Spikes found in '$rrdfile'" . ($html ? "</td></tr>\n" : "\n"); 395 } 396} 397 398/* finally update the file XML file and Reprocess the RRDfile */ 399if (! $dryrun) { 400 if ($total_kills) { 401 if (writeXMLFile($new_output, $xmlfile)) { 402 if (backupRRDFile($rrdfile)) { 403 createRRDFileFromXML($xmlfile, $rrdfile); 404 } else { 405 echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . "FATAL: Unable to backup '$rrdfile'" . ($html ? "</td></tr>\n" : "\n"); 406 } 407 } else { 408 echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . "FATAL: Unable to write XML file '$xmlfile'" . ($html ? "</td></tr>\n" : "\n"); 409 } 410 } 411} else { 412 echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . 'NOTE: Dryrun requested. No updates performed' . ($html ? "</td></tr>\n" : "\n"); 413} 414 415if ($html) { 416 echo '</table>'; 417} 418 419/* All Functions */ 420function createRRDFileFromXML($xmlfile, $rrdfile) 421{ 422 global $using_cacti, $html; 423 424 /* execute the dump command */ 425 echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . "NOTE: Re-Importing '$xmlfile' to '$rrdfile'" . ($html ? "</td></tr>\n" : "\n"); 426 if ($using_cacti) { 427 $response = shell_exec(read_config_option('path_rrdtool') . " restore -f -r $xmlfile $rrdfile"); 428 } else { 429 $response = shell_exec("rrdtool restore -f -r $xmlfile $rrdfile"); 430 } 431 if (strlen($response)) { 432 echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . $response . ($html ? "</td></tr>\n" : "\n"); 433 } 434} 435 436function writeXMLFile($output, $xmlfile) 437{ 438 return file_put_contents($xmlfile, $output); 439} 440 441function backupRRDFile($rrdfile) 442{ 443 global $using_cacti, $tempdir, $seed, $html; 444 445 if ($using_cacti) { 446 $backupdir = read_config_option('spikekill_backupdir'); 447 448 if ($backupdir == '') { 449 $backupdir = $tempdir; 450 } 451 } else { 452 $backupdir = $tempdir; 453 } 454 455 if (file_exists($backupdir . '/' . basename($rrdfile))) { 456 $newfile = basename($rrdfile) . '.' . $seed; 457 } else { 458 $newfile = basename($rrdfile); 459 } 460 461 echo($html ? "<tr><td colspan='20' class='spikekill_note'>" : '') . "NOTE: Backing Up '$rrdfile' to '" . $backupdir . '/' . $newfile . "'" . ($html ? "</td></tr>\n" : "\n"); 462 463 return copy($rrdfile, $backupdir . '/' . $newfile); 464} 465 466function calculateVarianceAverages(&$rra, &$samples) 467{ 468 global $outliers; 469 470 if (sizeof($samples)) { 471 foreach ($samples as $rra_num => $dses) { 472 if (sizeof($dses)) { 473 foreach ($dses as $ds_num => $ds) { 474 if (sizeof($ds) < $outliers * 3) { 475 $rra[$rra_num][$ds_num]['variance_avg'] = 'NAN'; 476 } else { 477 rsort($ds, SORT_NUMERIC); 478 $ds = array_slice($ds, $outliers); 479 480 sort($ds, SORT_NUMERIC); 481 $ds = array_slice($ds, $outliers); 482 483 $rra[$rra_num][$ds_num]['variance_avg'] = array_sum($ds) / sizeof($ds); 484 } 485 } 486 } 487 } 488 } 489} 490 491function calculateOverallStatistics(&$rra, &$samples) 492{ 493 global $percent, $stddev, $ds_min, $ds_max, $var_kills, $std_kills; 494 495 $rra_num = 0; 496 if (sizeof($rra)) { 497 foreach ($rra as $dses) { 498 $ds_num = 0; 499 500 if (sizeof($dses)) { 501 foreach ($dses as $ds) { 502 if (isset($samples[$rra_num][$ds_num])) { 503 $rra[$rra_num][$ds_num]['standard_deviation'] = standard_deviation($samples[$rra_num][$ds_num]); 504 if ($rra[$rra_num][$ds_num]['standard_deviation'] == 'NAN') { 505 $rra[$rra_num][$ds_num]['standard_deviation'] = 0; 506 } 507 $rra[$rra_num][$ds_num]['average'] = $rra[$rra_num][$ds_num]['sumofsamples'] / $rra[$rra_num][$ds_num]['numsamples']; 508 509 $rra[$rra_num][$ds_num]['min_cutoff'] = $rra[$rra_num][$ds_num]['average'] - ($stddev * $rra[$rra_num][$ds_num]['standard_deviation']); 510 if ($rra[$rra_num][$ds_num]['min_cutoff'] < $ds_min[$ds_num]) { 511 $rra[$rra_num][$ds_num]['min_cutoff'] = $ds_min[$ds_num]; 512 } 513 514 $rra[$rra_num][$ds_num]['max_cutoff'] = $rra[$rra_num][$ds_num]['average'] + ($stddev * $rra[$rra_num][$ds_num]['standard_deviation']); 515 if ($rra[$rra_num][$ds_num]['max_cutoff'] > $ds_max[$ds_num]) { 516 $rra[$rra_num][$ds_num]['max_cutoff'] = $ds_max[$ds_num]; 517 } 518 519 $rra[$rra_num][$ds_num]['numnksamples'] = 0; 520 $rra[$rra_num][$ds_num]['sumnksamples'] = 0; 521 $rra[$rra_num][$ds_num]['avgnksamples'] = 0; 522 523 /* go through values and find cutoffs */ 524 $rra[$rra_num][$ds_num]['stddev_killed'] = 0; 525 $rra[$rra_num][$ds_num]['variance_killed'] = 0; 526 527 if (sizeof($samples[$rra_num][$ds_num])) { 528 foreach ($samples[$rra_num][$ds_num] as $sample) { 529 if (($sample > $rra[$rra_num][$ds_num]['max_cutoff']) || 530 ($sample < $rra[$rra_num][$ds_num]['min_cutoff'])) { 531 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)))); 532 $rra[$rra_num][$ds_num]['stddev_killed']++; 533 $std_kills = true; 534 } else { 535 $rra[$rra_num][$ds_num]['numnksamples']++; 536 $rra[$rra_num][$ds_num]['sumnksamples'] += $sample; 537 } 538 539 if ($rra[$rra_num][$ds_num]['variance_avg'] == 'NAN') { 540 /* not enought samples to calculate */ 541 } elseif ($sample > ($rra[$rra_num][$ds_num]['variance_avg'] * (1 + $percent))) { 542 /* kill based upon variance */ 543 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)))); 544 $rra[$rra_num][$ds_num]['variance_killed']++; 545 $var_kills = true; 546 } 547 } 548 } 549 550 if ($rra[$rra_num][$ds_num]['numnksamples'] > 0) { 551 $rra[$rra_num][$ds_num]['avgnksamples'] = $rra[$rra_num][$ds_num]['sumnksamples'] / $rra[$rra_num][$ds_num]['numnksamples']; 552 } 553 } else { 554 $rra[$rra_num][$ds_num]['standard_deviation'] = 'N/A'; 555 $rra[$rra_num][$ds_num]['average'] = 'N/A'; 556 $rra[$rra_num][$ds_num]['min_cutoff'] = 'N/A'; 557 $rra[$rra_num][$ds_num]['max_cutoff'] = 'N/A'; 558 $rra[$rra_num][$ds_num]['numnksamples'] = 'N/A'; 559 $rra[$rra_num][$ds_num]['sumnksamples'] = 'N/A'; 560 $rra[$rra_num][$ds_num]['avgnksamples'] = 'N/A'; 561 $rra[$rra_num][$ds_num]['stddev_killed'] = 'N/A'; 562 $rra[$rra_num][$ds_num]['variance_killed'] = 'N/A'; 563 $rra[$rra_num][$ds_num]['stddev_killed'] = 'N/A'; 564 $rra[$rra_num][$ds_num]['numnksamples'] = 'N/A'; 565 $rra[$rra_num][$ds_num]['sumnksamples'] = 'N/A'; 566 $rra[$rra_num][$ds_num]['variance_killed'] = 'N/A'; 567 $rra[$rra_num][$ds_num]['avgnksamples'] = 'N/A'; 568 } 569 570 $ds_num++; 571 } 572 } 573 574 $rra_num++; 575 } 576 } 577} 578 579function outputStatistics($rra) 580{ 581 global $rra_cf, $rra_name, $ds_name, $rra_pdp, $html; 582 583 if (sizeof($rra)) { 584 if (! $html) { 585 echo "\n"; 586 printf( 587 "%10s %16s %10s %7s %7s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n", 588 'Size', 589 'DataSource', 590 'CF', 591 'Samples', 592 'NonNan', 593 'Avg', 594 'StdDev', 595 'MaxValue', 596 'MinValue', 597 'MaxStdDev', 598 'MinStdDev', 599 'StdKilled', 600 'VarKilled', 601 'StdDevAvg', 602 'VarAvg' 603 ); 604 printf( 605 "%10s %16s %10s %7s %7s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n", 606 '----------', 607 '---------------', 608 '----------', 609 '-------', 610 '-------', 611 '----------', 612 '----------', 613 '----------', 614 '----------', 615 '----------', 616 '----------', 617 '----------', 618 '----------', 619 '----------', 620 '----------' 621 ); 622 foreach ($rra as $rra_key => $dses) { 623 if (sizeof($dses)) { 624 foreach ($dses as $dskey => $ds) { 625 printf( 626 '%10s %16s %10s %7s %7s ' . 627 ($ds['average'] < 1E6 ? '%10s ' : '%10.4e ') . 628 ($ds['standard_deviation'] < 1E6 ? '%10s ' : '%10.4e ') . 629 (isset($ds['max_value']) ? ($ds['max_value'] < 1E6 ? '%10s ' : '%10.4e ') : '%10s ') . 630 (isset($ds['min_value']) ? ($ds['min_value'] < 1E6 ? '%10s ' : '%10.4e ') : '%10s ') . 631 (isset($ds['max_cutoff']) ? ($ds['max_cutoff'] < 1E6 ? '%10s ' : '%10.4e ') : '%10s ') . 632 (isset($ds['min_cutoff']) ? ($ds['min_cutoff'] < 1E6 ? '%10s ' : '%10.4e ') : '%10s ') . 633 '%10s %10s ' . 634 (isset($ds['avgnksampled']) ? ($ds['avgnksamples'] < 1E6 ? '%10s ' : '%10.4e ') : '%10s ') . 635 (isset($ds['variance_avg']) ? ($ds['variance_avg'] < 1E6 ? '%10s ' : '%10.4e ') : '%10s ') . "\n", 636 displayTime($rra_pdp[$rra_key]), 637 $ds_name[$dskey], 638 $rra_cf[$rra_key], 639 $ds['totalsamples'], 640 (isset($ds['numsamples']) ? $ds['numsamples'] : '0'), 641 ($ds['average'] != 'N/A' ? round($ds['average'], 2) : $ds['average']), 642 ($ds['standard_deviation'] != 'N/A' ? round($ds['standard_deviation'], 2) : $ds['standard_deviation']), 643 (isset($ds['max_value']) ? round($ds['max_value'], 2) : 'N/A'), 644 (isset($ds['min_value']) ? round($ds['min_value'], 2) : 'N/A'), 645 ($ds['max_cutoff'] != 'N/A' ? round($ds['max_cutoff'], 2) : $ds['max_cutoff']), 646 ($ds['min_cutoff'] != 'N/A' ? round($ds['min_cutoff'], 2) : $ds['min_cutoff']), 647 $ds['stddev_killed'], 648 $ds['variance_killed'], 649 ($ds['avgnksamples'] != 'N/A' ? round($ds['avgnksamples'], 2) : $ds['avgnksamples']), 650 (isset($ds['variance_avg']) ? round($ds['variance_avg'], 2) : 'N/A') 651 ); 652 } 653 } 654 } 655 656 echo "\n"; 657 } else { 658 printf( 659 "<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", 660 'Size', 661 'DataSource', 662 'CF', 663 'Samples', 664 'NonNan', 665 'Avg', 666 'StdDev', 667 'MaxValue', 668 'MinValue', 669 'MaxStdDev', 670 'MinStdDev', 671 'StdKilled', 672 'VarKilled', 673 'StdDevAvg', 674 'VarAvg' 675 ); 676 foreach ($rra as $rra_key => $dses) { 677 if (sizeof($dses)) { 678 foreach ($dses as $dskey => $ds) { 679 printf( 680 '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>' . 681 ($ds['average'] < 1E6 ? '%s</td><td>' : '%.4e</td><td>') . 682 ($ds['standard_deviation'] < 1E6 ? '%s</td><td>' : '%.4e</td><td>') . 683 (isset($ds['max_value']) ? ($ds['max_value'] < 1E6 ? '%s</td><td>' : '%.4e</td><td>') : '%s</td><td>') . 684 (isset($ds['min_value']) ? ($ds['min_value'] < 1E6 ? '%s</td><td>' : '%.4e</td><td>') : '%s</td><td>') . 685 (isset($ds['max_cutoff']) ? ($ds['max_cutoff'] < 1E6 ? '%s</td><td>' : '%.4e</td><td>') : '%s</td><td>') . 686 (isset($ds['min_cutoff']) ? ($ds['min_cutoff'] < 1E6 ? '%s</td><td>' : '%.4e</td><td>') : '%s</td><td>') . 687 '%s</td><td>%s</td><td>' . 688 (isset($ds['avgnksampled']) ? ($ds['avgnksamples'] < 1E6 ? '%s</td><td>' : '%.4e</td><td>') : '%s</td><td>') . 689 (isset($ds['variance_avg']) ? ($ds['variance_avg'] < 1E6 ? "%s</td></tr>\n" : "%.4e</td></tr>\n") : "%s</td></tr>\n") . "\n", 690 displayTime($rra_pdp[$rra_key]), 691 $ds_name[$dskey], 692 $rra_cf[$rra_key], 693 $ds['totalsamples'], 694 (isset($ds['numsamples']) ? $ds['numsamples'] : '0'), 695 ($ds['average'] != 'N/A' ? round($ds['average'], 2) : $ds['average']), 696 ($ds['standard_deviation'] != 'N/A' ? round($ds['standard_deviation'], 2) : $ds['standard_deviation']), 697 (isset($ds['max_value']) ? round($ds['max_value'], 2) : 'N/A'), 698 (isset($ds['min_value']) ? round($ds['min_value'], 2) : 'N/A'), 699 ($ds['max_cutoff'] != 'N/A' ? round($ds['max_cutoff'], 2) : $ds['max_cutoff']), 700 ($ds['min_cutoff'] != 'N/A' ? round($ds['min_cutoff'], 2) : $ds['min_cutoff']), 701 $ds['stddev_killed'], 702 $ds['variance_killed'], 703 ($ds['avgnksamples'] != 'N/A' ? round($ds['avgnksamples'], 2) : $ds['avgnksamples']), 704 (isset($ds['variance_avg']) ? round($ds['variance_avg'], 2) : 'N/A') 705 ); 706 } 707 } 708 } 709 } 710 } 711} 712 713function updateXML(&$output, &$rra) 714{ 715 global $numspike, $percent, $avgnan, $method, $total_kills; 716 $new_array = []; 717 718 /* variance subroutine */ 719 $rra_num = 0; 720 $ds_num = 0; 721 $kills = 0; 722 723 if (sizeof($output)) { 724 foreach ($output as $line) { 725 if (substr_count($line, '<v>')) { 726 $linearray = explode('<v>', $line); 727 /* discard the row */ 728 array_shift($linearray); 729 730 /* initialize variables */ 731 $ds_num = 0; 732 $out_row = '<row>'; 733 foreach ($linearray as $dsvalue) { 734 /* peel off garbage */ 735 $dsvalue = trim(str_replace('</row>', '', str_replace('</v>', '', $dsvalue))); 736 if (strtolower($dsvalue) == 'nan') { 737 /* do nothing, it's a NaN */ 738 } else { 739 if ($method == 2) { 740 if ($dsvalue > (1 + $percent) * $rra[$rra_num][$ds_num]['variance_avg']) { 741 if ($kills < $numspike) { 742 if ($avgnan == 'avg') { 743 $dsvalue = $rra[$rra_num][$ds_num]['variance_avg']; 744 } else { 745 $dsvalue = 'NaN'; 746 } 747 $kills++; 748 $total_kills++; 749 } 750 } 751 } else { 752 if (($dsvalue > $rra[$rra_num][$ds_num]['max_cutoff']) || 753 ($dsvalue < $rra[$rra_num][$ds_num]['min_cutoff'])) { 754 if ($kills < $numspike) { 755 if ($avgnan == 'avg') { 756 $dsvalue = $rra[$rra_num][$ds_num]['average']; 757 } else { 758 $dsvalue = 'NaN'; 759 } 760 $kills++; 761 $total_kills++; 762 } 763 } 764 } 765 } 766 767 $out_row .= '<v> ' . $dsvalue . '</v>'; 768 $ds_num++; 769 } 770 771 $out_row .= '</row>'; 772 773 $new_array[] = $out_row; 774 } else { 775 if (substr_count($line, '</rra>')) { 776 $ds_minmax = []; 777 $rra_num++; 778 $kills = 0; 779 } elseif (substr_count($line, '</database>')) { 780 $ds_num++; 781 $kills = 0; 782 } 783 784 $new_array[] = $line; 785 } 786 } 787 } 788 789 return $new_array; 790} 791 792function removeComments(&$output) 793{ 794 $new_array = []; 795 if (sizeof($output)) { 796 foreach ($output as $line) { 797 $line = trim($line); 798 if ($line == '') { 799 continue; 800 } else { 801 /* is there a comment, remove it */ 802 $comment_start = strpos($line, '<!--'); 803 if ($comment_start === false) { 804 /* do nothing no line */ 805 } else { 806 $comment_end = strpos($line, '-->'); 807 if ($comment_start == 0) { 808 $line = trim(substr($line, $comment_end + 3)); 809 } else { 810 $line = trim(substr($line, 0, $comment_start - 1) . substr($line, $comment_end + 3)); 811 } 812 } 813 814 if ($line != '') { 815 $new_array[] = $line; 816 } 817 } 818 } 819 /* transfer the new array back to the original array */ 820 return $new_array; 821 } 822} 823 824function displayTime($pdp) 825{ 826 global $step; 827 828 $total_time = $pdp * $step; // seconds 829 830 if ($total_time < 60) { 831 return $total_time . ' secs'; 832 } else { 833 $total_time = $total_time / 60; 834 835 if ($total_time < 60) { 836 return $total_time . ' mins'; 837 } else { 838 $total_time = $total_time / 60; 839 840 if ($total_time < 24) { 841 return $total_time . ' hours'; 842 } else { 843 $total_time = $total_time / 24; 844 845 return $total_time . ' days'; 846 } 847 } 848 } 849} 850 851function debug($string) 852{ 853 global $debug; 854 855 if ($debug) { 856 echo 'DEBUG: ' . $string . "\n"; 857 } 858} 859 860function standard_deviation($samples) 861{ 862 $sample_count = count($samples); 863 $sample_square = []; 864 865 for ($current_sample = 0; $sample_count > $current_sample; $current_sample++) { 866 $sample_square[$current_sample] = pow($samples[$current_sample], 2); 867 } 868 869 return sqrt(array_sum($sample_square) / $sample_count - pow((array_sum($samples) / $sample_count), 2)); 870} 871 872/* display_help - displays the usage of the function */ 873function display_help() 874{ 875 global $using_cacti; 876 877 if ($using_cacti) { 878 $version = spikekill_version(); 879 } else { 880 $version = 'v1.0'; 881 } 882 883 echo 'Cacti Spike Remover ' . ($using_cacti ? 'v' . $version['version'] : $version) . ", Copyright 2009, The Cacti Group, Inc.\n\n"; 884 echo "Usage:\n"; 885 echo "removespikes.php -R|--rrdfile=rrdfile [-M|--method=stddev] [-A|--avgnan] [-S|--stddev=N]\n"; 886 echo " [-P|--percent=N] [-N|--number=N] [-D|--dryrun] [-d|--debug] [-h|--help|-v|-V|--version]\n\n"; 887 888 echo "The RRDfile input parameter is mandatory. If no other input parameters are specified the defaults\n"; 889 echo "are taken from the Spikekill Plugin settings.\n\n"; 890 891 echo "-M|--method - The spike removal method to use. Options are 'stddev'|'variance'\n"; 892 echo "-A|--avgnan - The spike replacement method to use. Options are 'avg'|'nan'\n"; 893 echo "-S|--stddev - The number of standard deviations +/- allowed\n"; 894 echo "-P|--percent - The sample to sample percentage variation allowed\n"; 895 echo "-N|--number - The maximum number of spikes to remove from the RRDfile\n"; 896 echo "-D|--dryrun - If specified, the RRDfile will not be changed. Instead a summary of\n"; 897 echo " changes that would have been performed will be issued.\n\n"; 898 899 echo "The remainder of arguments are informational\n"; 900 echo "-d|--debug - Display verbose output during execution\n"; 901 echo "-v -V --version - Display this help message\n"; 902 echo "-h --help - display this help message\n"; 903} 904