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