#!/usr/local/cpanel/3rdparty/bin/php
<?php
// Constants
define("VERSION", "2.2.2");
define("UPDATED", "Aug 21 2020");

// Enable error reporting
ini_set("error_reporting", E_ALL);
ini_set("log_errors", "Off");
ini_set("display_errors", "On");
ini_set("date.timezone", "America/New_York");

// If the timezone is not valid, then fall back to EST
if (!in_array(ini_get("date.timezone"), timezone_identifiers_list())) {
    ini_set("date.timezone", "America/New_York");
    echo "\"" . ini_set("date.timezone") . "\" is not a valid timezone. Defaulting to EST.";
}
error_reporting(-1);

/* Start of SnapParse Class */
/* Start of SnapParse Class */
/* Start of SnapParse Class */
/* Start of SnapParse Class */

class SnapParse
{
    /* Changeable Values */
    public $shell_wrapped = false; // When set to false, the script will print the line to use the "escalated" script
    public $allow_shell_exec = false; // When set to true, enables use of "clear" as well as netstat one-liner for connections
    public $enable_colors = true; // Whether to use colors or not
    public $show_current_information = false; // Display current server information
    public $force_log_display = false; // Force logs to be displayed regardless of load threshold
    public $debug_level = 0; // Debug level
    
    /* Storage values */
    public $set_log = NULL; // Set which log to select automatically
    public $set_action = NULL; // Set which action to select automatically
    public $scan_type = NULL; // Which scan type to use
    public $target_dir = NULL; // Directory to search for files as well as its type (ordered)
    public $scan_dirs = [
        // HostDime-patched sys-snap location
        [
            "pos" => 1,
            "path" => "/var/log/system-snapshot",
            "type" => "syssnap"
        ],
        // Default sys-snap location
        [
            "pos" => 2,
            "path" => "/root/system-snapshot",
            "type" => "syssnap"
        ],
        // Default axon location
        [
            "pos" => 3,
            "path" => "/var/log/axond",
            "type" => "axon"
        ]
    ];
    public $log_unix = -1; // The selected log (unix time only)
    public $log = ""; // The selected log (unix time + dot, if it exists)
    public $selected_log = -1; // The index of the selected log date
    public $log_contents = []; // Log contents array
    public $logs_were_found = false; // Flag if logs were found to switch the type of error
    public $log_db = []; // Array where item dates will be stored
    public $failures = 0; // Number of prompt failures
    public $cpu_threads = 0; // Number of threads
    public $overload_warning_threshold = 0; // Overload threshold
    public $overload_danger_threshold = 1; // Overload threshold
    public $peak_load = 0; // Peak load used to keep track of load detection
    public $colors = [ // Colors
        "reset"        => "0",         // RESET
        "underline"    => "2",         // UNDERLINE
        "white"        => "97",        // WHITE
        "red"          => "31",        // RED
        "pink"         => "91",        // LIGHT RED
        "gold"         => "33",        // GOLD
        "yellow"       => "93",        // YELLOW
        "green"        => "32",        // GREEN
        "blue"         => "34",        // BLUE
        "cyan"         => "36",        // CYAN
        "magenta"      => "95",        // MAGENTA
        "gray"         => "90",        // GRAY
        "border"       => "3",         // BORDER (BLACK ON LIGHT GRAY)
        "wall"         => "3",         // WALL (LIGHT GRAY ON DARK GRAY)
        "warning"      => "43",        // WHITE ON YELLOW
        "urgent"       => "3;31",      // RED ON BLACK
        "almostbad"    => "3;5;101",   // RED ON WHITE
        "bad"          => "41",        // WHITE ON RED
    ];
    public $option_available = [
        "exim"  => false,
        "files" => false,
        "http"  => false,
        "mysql" => false,
        "net"   => false,
        "proc"  => false,
    ];
    
    // Class init
    public function __construct($arguments) {
        $this->processArguments($arguments);
        
        // Check for shell_exec
        if (trim(shell_exec("echo test")) == "test") {
            $this->allow_shell_exec = true;
        } else {
            $this->allow_shell_exec = false;
        }
        $this->nproc();
        
        $this->overload_warning_threshold = $this->nproc() - 1;
        $this->overload_danger_threshold = $this->nproc();
        
        if ($this->debug_level) {
            $this->overload_warning_threshold = 0.1;
            $this->overload_danger_threshold = 0.2;
        }
        
        $this->initScan();
    }
    
    public function initScan() {
        $this->detectScanType(true);
        
        // Go through interactive prompt if a specific file is not used
        if ($this->show_current_information) {
            $this->askMethod();
        } else {
            if (empty($this->log)) {
                $this->getLogs();
                if (empty($this->set_log))
                    $this->showLogs(true);
                $this->askLog();
            }
        }
    }
    
    /* Functions */
    
    // Process arguments
    public function processArguments($arguments = []) {
        foreach ($arguments as $key=>$arg) {
            $arg = strtolower(preg_replace("/^\-\-?/", "", trim($arg)));
            
            switch (true) {                    
                // Enable debug
                case ($arg == "debug"):
                    $this->debug_level++;
                    break;
                    
                // Enable force log display
                case ($arg == "force"):
                    $this->force_log_display = true;
                    break;
                    
                // Enable current log display
                case ($arg == "now"):
                    $this->show_current_information = true;
                    break;
                
                /* 
                // Action: Exim
                case ($arg == "exim"):
                case ($arg == "mail"):
                    $this->set_action = 1;
                    break;
                
                // Action: Files
                case ($arg == "file"):
                case ($arg == "files"):
                case ($arg == "lsof"):
                    $this->set_action = 2;
                    break;
                
                // Action: HTTP
                case ($arg == "http"):
                case ($arg == "httpd"):
                case ($arg == "apache"):
                    $this->set_action = 3;
                    break;
                
                // Action: MySQL
                case ($arg == "sql"):
                case ($arg == "mysql"):
                    $this->set_action = 4;
                    break;
                
                // Action: Network
                case ($arg == "net"):
                case ($arg == "network"):
                case ($arg == "netstat"):
                    $this->set_action = 5;
                    break;
                
                // Action: Processes
                case ($arg == "proc"):
                case ($arg == "processes"):
                    $this->set_action = 6;
                    break;
                
                // Action: Processes (Processes - Active)
                case ($arg == "instant"):
                case ($arg == "active"):
                    $this->set_action = "6b";
                    break;
                */
                
                // Set axon directory
                case (file_exists($arg) && is_dir($arg)):
                    array_push($this->scan_dirs, [
                        "pos" => 0,
                        "path" => $arg,
                        "type" => "auto"
                    ]);
                    break;
                
                // Set target file
                case (file_exists($arg) && !is_dir($arg) && strpos($arg, "/dev") !== 0):
                    $this->log = $arg;
                    $this->target_directLoad($arg);
                    break;
                    
                // Try to set which action based on input
                case (strval(intval($arg)) == strval($arg)):
                    $this->set_action = intval($arg);
                    break;
            }
        }
    }
    
    // Clears the screen if shell_exec is enabled
    public function clear() {
        if ($this->allow_shell_exec) echo shell_exec("clear");
    }
    
    // Changes color
    public function c($color = "reset") {
        if (!$this->enable_colors)
            return "";
        
        if (isset($this->colors[$color]))
            return chr(27)."[" . $this->colors[$color] . "m";
        
        return "";
    }
    
    // Highlights based on a set scale
    // Color order: Blue, Green, Yellow, Red, Bad
    public function scale($value, $scale = 100, $start = 0, $padding = 0, $add = "", $direction = STR_PAD_LEFT) {
        // Try to remove extra spacing and convert it to an integer
        if (trim($value) == floatval(trim($value))) {
            $value = floatval($value);
        } else if (trim($value) == intval(trim($value))) {
            $value = intval($value);
        }
        
        // This section is necessary to prevent color backgorunds from displaying across an entire block
        // It also adds padding (used in returned values that contain colors)
        $print_padding = str_pad(str_replace($value, "", $value), $padding - strlen($value), " ", $direction);
        
        // If the input is not an integer, just toss it right back.
        // $start would be the minimum value where you should start triggering additive scaling
        if (is_int($value) || is_float($value)) {
            
            // Special case for load values
            /* if (is_float($value)) {
                if ($value === floatval(0)) {
                    $print_padding = "";
                    $value = number_format($value, $padding - 2);
                } else {
                    $print_padding = "";
                    $value = number_format($value, $padding - 2);
                }
            } */
            
            $return_color = "";
            // Check to see if the scale is in simple increments
            if (is_int($scale) || is_float($scale)) {
                switch (true) {
                    case ($value < $scale + $start):
                        return $print_padding . $this->c() . $value . $add . $this->c();
                        break;
                    case ($value < ($scale * 2) + $start):
                        $return_color = "green";
                        break;
                    case ($value < ($scale * 3) + $start):
                        $return_color = "yellow";
                        break;
                    case ($value < ($scale * 4) + $start):
                        $return_color = "pink";
                        break;
                    case ($value < ($scale * 5) + $start):
                        $return_color = "almostbad";
                        break;
                    default:
                        $return_color = "bad";
                        break;
                }
                return $print_padding . $this->c($return_color) . $value . $add . $this->c();
                
            } else if (is_object($scale) || is_array($scale)) {
                switch (true) {
                    case (isset($scale[0]) && $value < $scale[0]):
                        $return_color = "cyan";
                        break;
                    case (isset($scale[1]) && $value < $scale[1]):
                        $return_color = "green";
                        break;
                    case (isset($scale[2]) && $value < $scale[2]):
                        $return_color = "yellow";
                        break;
                    case (isset($scale[3]) && $value < $scale[3]):
                        $return_color = "pink";
                        break;
                    case (isset($scale[4]) && $value < $scale[4]):
                        $return_color = "almostbad";
                        break;
                    default:
                        $return_color = "bad";
                        break;
                }
                return $this->c($return_color) . $value . $add . $this->c();
                
            } else {
                return $print_padding . $value . $add;
            }
        } else {
            return $print_padding . $value . $add;
        }
    }
    
    public function orderByPosition($a, $b) {
        return $a["pos"] > $b["pos"];
    }
    
    // Checks which type of scan to use
    public function getScanType($rescan = false) {
        usort($this->scan_dirs, [$this, "orderByPosition"]);
        
        if (!$this->scan_type || $rescan) {
            $this->scan_type = "current";
            $this->target_dir = isset($_SERVER["SHELL"]) ? $_SERVER["SHELL"] : "/bin/sh";
            
            if ($this->show_current_information)
                return $this->scan_type;
            
            foreach ($this->scan_dirs as $scan_locale) {
                $scan_path = $scan_locale["path"];
                $scan_type = $scan_locale["type"];
                
                if ($this->scan_type === "current" && file_exists($scan_path) && is_dir($scan_path)) {
                    // Auto
                    if ($scan_type == "auto") {
                        // Auto-detect Axon
                        if (count(glob($scan_path . "/overload.*.log.*")) || count(glob($scan_path . "/smartctl.*.log"))) {
                            $this->scan_type = "axon";
                            $this->target_dir = $scan_path;
                            
                            return $this->scan_type;
                        // Auto-detect SysSnap
                        } else if (count(glob($scan_path . "/[0-9]*"))) {
                            $this->scan_type = "syssnap";
                            $this->target_dir = $scan_path;
                            
                            return $this->scan_type;
                        }
                    } else {
                        $this->scan_type = $scan_type;
                        $this->target_dir = $scan_path;
                        
                        return $this->scan_type;
                    }
                }
            }
        }
        
        return $this->scan_type;
    }
    
    // Force rescan of scan type
    public function detectScanType() {
        return $this->getScanType(true);
    }
    
    // Get number of CPUs
    public function nproc() {
        if ($this->cpu_threads)
            return $this->cpu_threads;
        
        $cpuinfo = file_get_contents("/proc/cpuinfo");
        $get_nproc = preg_match_all("/^processor\s*:/m", $cpuinfo, $proc_matches);
        $this->cpu_threads = $get_nproc ? count($proc_matches[0]) : 1;
        
        return $this->cpu_threads;
    }
    
    // Populates list of logs
    public function getLogs() {
        
        switch ($this->getScanType()) {
            // Checks that syssnap dir exists
            case "syssnap":
                $this->clear();
                $this->peak_load = 0;
                
                $syssnap_hour_files = array_diff(scandir($this->target_dir), [".", "..", "current"]);
                sort($syssnap_hour_files, SORT_NUMERIC);
                
                if (count($syssnap_hour_files))
                    $this->logs_were_found = true;
                    
                foreach ($syssnap_hour_files as $snap_hour) {
                    if (file_exists($this->target_dir . "/" . $snap_hour) && is_dir($this->target_dir . "/" . $snap_hour)) {
                        $syssnap_minute_files = array_diff(scandir($this->target_dir . "/" . $snap_hour), [".", ".."]);
                        sort($syssnap_minute_files, SORT_NUMERIC);
                        
                        foreach ($syssnap_minute_files as $snap_minute) {
                            $log_path = $this->target_dir . "/" . $snap_hour . "/" . $snap_minute;
                            if (file_exists($log_path)) {
                                // Gets the server load from the file
                                $handle_sl = fopen($log_path, "r");
                                $log_contents = fread($handle_sl, filesize($log_path));
                                fclose($handle_sl);
                                $get_load = preg_match("/Load Average\: ([0-9\.]+) ([0-9\.]+) ([0-9\.]+) ([0-9]+)\/([0-9]+)/", $log_contents, $load_matches);
                                if ($get_load) {
                                    $log_uptime_parse = [
                                        "path" => $snap_hour . "/" . $snap_minute,
                                        "fullpath" => $log_path,
                                        "date" => date("Y-m-d", filemtime($log_path)),
                                        "time" => str_pad($snap_hour, 2, "0", STR_PAD_LEFT) . ":" . str_pad(str_replace(".log", "", $snap_minute), 2, "0", STR_PAD_LEFT),
                                        "load" => [
                                            "1min" => floatval($load_matches[1]),
                                            "5min" => floatval($load_matches[2]),
                                            "10min" => floatval($load_matches[3]),
                                        ],
                                        "processes" => [
                                            "active" => intval($load_matches[4]),
                                            "total" => intval($load_matches[5]),
                                        ],
                                    ];
									// Handle syssnap timestamp differently. This is because the mtime timestamp for syssnap logs are typically one minute ahead of their filename
									$log_uptime_parse["unix"] = date("U", strtotime($log_uptime_parse["date"] . " " . $log_uptime_parse["time"]));
                                    
                                    if ($log_uptime_parse["load"]["1min"] > $this->peak_load)
                                        $this->peak_load = $log_uptime_parse["load"]["1min"];
                                    
                                    if ($log_uptime_parse["load"]["1min"] > $this->overload_warning_threshold || $this->force_log_display)
                                        array_push($this->log_db, $log_uptime_parse);
                                }
                                
                            }
                        }
                    }
                }
                
                usort($this->log_db, [$this, "sortByUnix"]);
                break;
            
            // Checks that axond exists
            case "axon":
                $this->clear();
                $axon_files = array_diff(scandir($this->target_dir), [".", ".."]);
                
                if (count($axon_files))
                    $this->logs_were_found = true;
                
                foreach ($axon_files as $file) {
                    if (strpos($file, "overload.") > -1) {
                        preg_match("/[0-9]+/", $file, $match);
                        if ((count($match) > 0) && (!in_array($match[0], $this->log_db))) {
                            array_push($this->log_db, $match[0]);
                        } else if ((count($match) == 0)  && (!in_array(-1, $this->log_db))) {
                            array_push($this->log_db, -1);
                        }
                    }
                }
                
                sort($this->log_db);
                break;
        }
        
    }
    
    // Load a file directly
    public function directLoad($filepath) {
        // Attempt to determine the type of file
        $this->log = $filepath;
        $handle_file = fopen($this->log, "r");
        $file_contents = fread($handle_file, filesize($this->log));
        fclose($handle_file);
        
        $file_type_match = [];
        // [exim]
        preg_match_all("/^\d+[hm]?\s+\d+[KMG]?\s+[A-Za-z0-9]{6}\-[A-Za-z0-9]{6}\-[A-Za-z0-9]{2}\s+\</m", $file_contents, $file_type_match["exim"]);
        // [mysql]
        preg_match_all("/^\|\s+Id\s+\|\s+User\s+\|\s+Host\s+\|\s+db\s+\|\s+Command\s+\|\s+Time\s+\|\s+State\s+\|\s+Info\s+\|\s+Progress\s+\|$/m", $file_contents, $file_type_match["mysql"]);
        // [net]
        preg_match_all("/^Active Internet connections/m", $file_contents, $file_type_match["net"]);
        // [proc]
        preg_match_all("/^USER\s+PID\s+%CPU\s+%MEM\s+VSZ\s+RSS\s+TTY\s+STAT\s+START\s+TIME\s+COMMAND$/m", $file_contents, $file_type_match["proc"]);
        
        switch (true) {
            // Match exim format
            case (count($file_type_match["exim"][0]) > 0):
                $this->clear();
                $this->parseExim($file_contents);
                break;
            
            // Match SQL format
            case (count($file_type_match["mysql"][0]) > 0):
                $this->clear();
                $this->parseMySQL($file_contents);
                break;
            
            // Match netstat format
            case (count($file_type_match["net"][0]) > 0):
                $this->clear();
                $this->parseNetTCP(1, $file_contents);
                break;
                
            // Match ps format
            case (count($file_type_match["proc"][0]) > 0):
                $this->clear();
                $this->parseProc(false, $file_contents);
                break;
        }
    }
    
    // Asks which log to use
    public function askLog($back = false) {
		while (empty($this->set_log) || !preg_match("/^[\w:]+$/i", $this->set_log)) {
			if (!empty($this->set_log))
				echo "{$this->c('yellow')}Invalid log selection.{$this->c()}\n";
			
			if ($this->scan_type == "syssnap")
				echo "\nWhich log number or time (e.g. 22:00, 10:00pm) would you like to analyze?\n";
			else
				echo "\nWhich log would you like to analyze?\n";
				
            $handle = fopen("php://stdin","r");
            $this->set_log = trim(fgets($handle));
        }
		
		$selected_log = $this->set_log;
        
        // Use current information
        $this->show_current_information = preg_match("/^(c|current|n|now)$/", trim(strtolower($selected_log)));
        if ($this->show_current_information)
            return $this->askMethod();
        
        switch ($this->getScanType()) {
            case "syssnap":
                // syssnap accepts:
                //  #               log position
                //  ##:##           time (24 hr)
                //  ##:##a,##:##p   time (12 hr)
                $selected_log_syssnap = [
                    "([0-9]+)", // position
                    "((?:[01]?[0-9]|2[0-3]):[0-5][0-9])", // 24 hour
                    "((?:0?[0-9]|1[0-2]):[0-5][0-9]\s*[ap]m?)", // 12 hour
                    "((?:0?[0-9]|1[0-2])\s*[ap]m?)", // 12 hour, minuteless (i.e. 3pm or 3p)
                ];
                
                $check_selected_log = preg_match_all("/^(?:" . implode("|", $selected_log_syssnap) . ")$/i", trim($selected_log), $selected_log_matches);
				
                switch (true) {
                    case isset($selected_log_matches[1][0]) && !empty($selected_log_matches[1][0]):
                        $this->selected_log = intval($selected_log_matches[1][0]);
                        break;
                    
                    case isset($selected_log_matches[2][0]) && !empty($selected_log_matches[2][0]):
                        $log_match_24hr = preg_match("/([0-9]+):([0-9]+)/", $selected_log_matches[2][0], $log_match_24hr_matches);
                        $this->selected_log = [
                            "h" => intval($log_match_24hr_matches[1]),
                            "m" => intval($log_match_24hr_matches[2])
                        ];
                        break;
                        
                    case isset($selected_log_matches[3][0]) && !empty($selected_log_matches[3][0]):
                        $log_match_12hr = preg_match("/([0-9]+):([0-9]+)\s*([ap])m?/i", $selected_log_matches[3][0], $log_match_12hr_matches);
                        $this->selected_log = [
                            "h" => isset($log_match_12hr_matches[3]) && strtolower($log_match_12hr_matches[3]) == "p" ? intval($log_match_12hr_matches[1]) + 12 : intval($log_match_12hr_matches[1]),
                            "m" => intval($log_match_12hr_matches[2])
                        ];
                        break;
                        
                    case isset($selected_log_matches[4][0]) && !empty($selected_log_matches[4][0]):
                        $log_match_12hr_minutless = preg_match("/([0-9]+)\s*([ap])m?/i", $selected_log_matches[4][0], $log_match_12hr_minuteless_matches);
                        $this->selected_log = [
                            "h" => isset($log_match_12hr_minuteless_matches[2]) && strtolower($log_match_12hr_minuteless_matches[2]) == "p" ? intval($log_match_12hr_minuteless_matches[1]) + 12 : intval($log_match_12hr_minuteless_matches[1]),
                            "m" => 0
                        ];
                        break;
                        
                    default:
                        $this->selected_log = -1;
                        break;
                }
                break;
                
            default:
                // non-syssnap accepts:
                //  #  log position
                $this->selected_log = preg_match("/^([0-9]+)$/", trim($selected_log)) ? intval($selected_log) : -1;
                break;
        }
        
        if (is_int($this->selected_log) && $this->selected_log == count($this->log_db) + 1) {
            $this->show_current_information = true;
            $this->getScanType(true);
            
            return $this->askMethod();
        } else if (is_int($this->selected_log) && $this->selected_log > 0 && $this->selected_log <= count($this->log_db)) {
            switch ($this->getScanType()) {
                case "axon":
                    $this->log = isset($this->log_db[$this->selected_log - 1]) ? $this->log_db[$this->selected_log - 1] : -1;
        
                    // Correct log ending for filenames
                    if ($this->log == -1) {
                        $this->log_unix = "now";
                        $this->log = "";
                    } else {
                        $this->log_unix = $this->log;
                        $this->log = "." . $this->log;
                    }
                    
                    return $this->askMethod();
                    break;
                
                case "syssnap":
                    $this->log = $this->log_db[$this->selected_log - 1];
                    
                    return $this->askMethod();
                    break;
            }
        } else if ($this->getScanType() == "syssnap" && is_array($this->selected_log)) {
            foreach($this->log_db as $index=>$log)
                if ($log["path"] == $this->selected_log["h"] . "/" . $this->selected_log["m"] . ".log") {
                    $this->selected_log = $index + 1;
                    $this->log = $log;
                }
                
            return $this->askMethod();
        } else {
			$this->set_log = false;
            $this->failures++;
            echo "{$this->c("yellow")}Invalid log\n{$this->c()}";
            if ($this->failures > 3) {
                echo "\nToo many failures. Exiting...\n";
                die;
            } else {
                return $this->askLog();
            }
        }
    }
        
    // Asks what section to parse
    public function askMethod() {
        $this->detectScanType();
        
        $method_display = [
            "current" => <<< EOF
1. Exim
2. Apache
3. MySQL
4. Network
5. Processes
6. Processes (Active)
EOF,
            "syssnap" => <<< EOF
{$this->c("gray")}1. Memory{$this->c()}
2. Processes
3. Processes (Active)
4. Network (TCP)
{$this->c("gray")}5. Network (Sockets){$this->c()}
{$this->c("gray")}6. Disk I/O{$this->c()}
7. MySQL
8. Apache
EOF,
            "axon" => <<< EOF
1. Exim
2. Apache
3. MySQL
4. Network
5. Processes
6. Processes (Active)
{$this->c("gray")}7. Files{$this->c()}
EOF
        ];
            
        if (!$this->set_action) {
            echo "\n";
            
            if ($this->show_current_information) {
                echo "{$this->c("magenta")}Showing Current Information{$this->c()} - {$this->c("green")}{$this->getLogTime()}{$this->c()}\n";
            } else {
                echo "{$this->c("green")}[{$this->getScanType()}] Selected Log: {$this->selected_log}. {$this->getLogTime()}{$this->c()}\n";
            }
            
            echo "Select Service/Log to Scan:\n";
            echo isset($method_display[$this->getScanType()]) ? $method_display[$this->getScanType()] : "";
            echo "\n-  Go back\n";
            
            echo "\nDisplay log type: ";
            $handle = fopen("php://stdin","r");
            $selected_method_input = trim(fgets($handle));
        } else {
            // Load action from user input
            $selected_method_input = $this->set_action;
        }
        
        // Clean input
        if (preg_match("/^\-$/", trim($selected_method_input))) {
            $selected_method = "-";            
            $this->show_current_information = false;
            $this->initScan();
        } else if (preg_match("/^[0-9]+$/", trim($selected_method_input))) {
            $selected_method = intval(trim($selected_method_input));
        }
        
        switch ($this->getScanType()) {
            case "current":
                switch ($selected_method) {
                    // Go back
                    case "-":
						$this->set_log = false;
                        $this->clear();
                        $this->showLogs();
                        return $this->askLog(true);
                        break;
                        
                    // Available methods
                    case 1: // Exim
                    case 2: // Apache
                    case 3: // MySQL
                    case 4: // Network
                    case 5: // Processes
                    case 6: // Processes (Active)
                        $this->parseCurrentRouter($selected_method);
                        break;
                        
                    default:
                        echo "\n{$this->c("yellow")}Invalid method selected.{$this->c()}\n";
                        return $this->askMethod();
                        break;
                }
                break;
            case "axon":
                switch ($selected_method) {
                    // Go back
                    case "-":
						$this->set_log = false;
                        $this->clear();
                        $this->showLogs();
                        return $this->askLog(true);
                        break;
                        
                    // Available methods
                    case 1: // Exim
                    case 2: // HTTP
                    case 3: // MySQL
                    case 4: // Network
                    case 5: // Processes
                    case 6: // Processes (Active)
                        $this->parseAxonRouter($selected_method);
                        break;
                        
                    // Not implemented
                    case 7: // Files
                        echo "\n{$this->c("yellow")}Method not yet implemented.{$this->c()}\n";
                        return $this->askMethod();
                        break;
                        
                    default:
                        echo "\n{$this->c("yellow")}Invalid method selected.{$this->c()}\n";
                        return $this->askMethod();
                        break;
                }
                break;
                
            case "syssnap":
                switch ($selected_method) {
                    // Go back
                    case "-":
						$this->set_log = false;
                        $this->clear();
                        $this->showLogs();
                        return $this->askLog(true);
                        break;
                        
                    // Available methods
                    case 2: // Processes
                    case 3: // Processes (Active)
                    case 4: // Network (TCP)
                    case 7: // MySQL
                    case 8: // Apache
                        $this->parseSnapRouter($selected_method);
                        break;
                        
                    // Not implemented
                    case 1: // Memory
                    case 5: // Network (UNIX)
                    case 6: // Disk I/O
                        echo "\n{$this->c("yellow")}Method not yet implemented.{$this->c()}\n";
                        return $this->askMethod();
                        break;
                        
                    default:
                        echo "\n{$this->c("yellow")}Invalid method selected.{$this->c()}\n";
                        return $this->askMethod();
                        break;
                }
                break;
        }
        
        // Ask for method again
        return ($this->set_log !== false || $this->set_action !== false) ? true : $this->askMethod();
    }
    
    public function resultsHead($input) {
        echo "\tAnalyzing: {$input}\n";
        echo "{$this->c("urgent")} --------------------- Results below this line --------------------- {$this->c()}\n";
    }
    
    public function resultsFoot() {
        echo "{$this->c("urgent")} --------------------- Results below this line --------------------- {$this->c()}\n";
    }
    
    public function parseCurrentRouter($method = -1) {
        $this->clear();
        
        switch ($method) {
            // Exim
            case 1:
                $this->resultsHead("`exim -bp`");
                $this->parseProc();
                break;
                
            // Apache
            case 2:
                $this->resultsHead("`lynx --dump localhost/whm-server-status`");
                $this->parseApache();
                break;
                
            // MySQL
            case 3:
                $this->resultsHead("`mysqladmin proc stat`");
                $this->parseMySQL();
                break;
                
            // Network
            case 4:
                $this->resultsHead("`netstat -anp`");
                $this->parseSnapNetTCP();
                break;
                
            // Processes
            case 5:
                $this->resultsHead("`ps awwxf -o user:20,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,args`");
                $this->parseProc(false);
                break;
                
            // Processes (Active)
            case 6:
                $this->resultsHead("`ps awwxf -o user:20,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,args`");
                $this->parseProc(true);
                break;
        }
        $this->resultsFoot();
        
        return $this->askMethod();
    }
    
    public function parseSnapRouter($method = -1) {
        $this->clear();
        $this->resultsHead($this->log["fullpath"]);
        
        switch ($method) {
            // Processes
            case 2:
                $this->parseSnapProc();
                break;
                
            // Processes (Active)
            case 3:
                $this->parseSnapProc(true);
                break;
                
            // Network
            case 4:
                $this->parseSnapNetTCP();
                break;
                
            // MySQL
            case 7:
                $this->parseSnapMySQL();
                break;
                
            // Apache
            case 8:
                $this->parseSnapApache();
                break;
        }
        $this->resultsFoot();
        
        return $this->askMethod();
    }
    
    // Asks what to parse
    public function parseAxonRouter($method) {
        $this->clear();
        $resultline_top = "{$this->c("urgent")} --------------------- Results below this line --------------------- {$this->c()}\n";
        switch ($method) {
            case 1: // Exim
                if ($this->log_unix == "now") {
                    echo "\tAnalyzing: " . $this->c("cyan") . "`exim -bp`" . $this->c() . "\n";
                    echo $resultline_top;
                    $this->parseExim();
                } else if (file_exists($this->target_dir . "/overload.exim.log" . $this->log)) {
                    echo "\tAnalyzing: " . $this->target_dir . "/overload.exim.log" . $this->log . "\n";
                    echo $resultline_top;
                    $this->parseExim();
                } else {
                    echo "{$this->c("yellow")}Option \"Exim\" unavailable\n{$this->c()}";
                    return $this->askMethod();
                }
                break;
            case 2: // HTTP
                if ($this->log_unix == "now") {
                    echo "\tAnalyzing: " . $this->c("cyan") . "`lynx --dump localhost/whm-server-status`" . $this->c() . "\n";
                    echo $resultline_top;
                    $this->parseApache();
                } else if (file_exists($this->target_dir . "/overload.http.log" . $this->log)) {
                    echo "\tAnalyzing: " . $this->target_dir . "/overload.http.log" . $this->log . "\n";
                    echo $resultline_top;
                    $this->parseApache();
                } else {
                    echo "{$this->c("yellow")}Option \"HTTP\" unavailable\n{$this->c()}";
                    return $this->askMethod();
                }
                break;
            case 3: // MySQL
                if ($this->log_unix == "now") {
                    echo "\tAnalyzing: " . $this->c("cyan") . "`mysqladmin proc stat`" . $this->c() . "\n";
                    echo $resultline_top;
                    $this->parseMySQL();
                } else if (file_exists($this->target_dir . "/overload.mysql.log" . $this->log)) {
                    echo "\tAnalyzing: " . $this->target_dir . "/overload.mysql.log" . $this->log . "\n";
                    echo $resultline_top;
                    $this->parseMySQL();
                } else {
                    echo "{$this->c("yellow")}Option \"MySQL\" unavailable\n{$this->c()}";
                    return $this->askMethod();
                }
                break;
            case 4: // Network
                if ($this->log_unix == "now") {
                    echo "\tAnalyzing: " . $this->c("cyan") . "`netstat -pltuna`" . $this->c() . "\n";
                    echo $resultline_top;
                    $this->parseNetTCP();
                } else if (file_exists($this->target_dir . "/overload.net.log" . $this->log)) {
                    echo "\tAnalyzing: " . $this->target_dir . "/overload.net.log" . $this->log . "\n";
                    echo $resultline_top;
                    $this->parseNetTCP();
                } else {
                    echo "{$this->c("yellow")}Option \"Network\" unavailable\n{$this->c()}";
                    return $this->askMethod();
                }
                break;
            case 5: // Processes
                if ($this->log_unix == "now") {
                    echo "\tAnalyzing: " . $this->c("cyan") . "`ps auxf`" . $this->c() . "\n";
                    echo $resultline_top;
                    $this->parseProc();
                } else if (file_exists($this->target_dir . "/overload.procs.log" . $this->log)) {
                    echo "\tAnalyzing: " . $this->target_dir . "/overload.procs.log" . $this->log . "\n";
                    echo $resultline_top;
                    $this->parseProc();
                } else {
                    echo "{$this->c("yellow")}Option \"Processes\" unavailable\n{$this->c()}";
                    return $this->askMethod();
                }
                break;
            case 6: // Processes (Active)
                if ($this->log_unix == "now") {
                    echo "\tAnalyzing: " . $this->c("cyan") . "`ps auxf`" . $this->c() . "\n";
                    echo $resultline_top;
                    $this->parseProc(true);
                } else if (file_exists($this->target_dir . "/overload.procs.log" . $this->log)) {
                    echo "\tAnalyzing: " . $this->target_dir . "/overload.procs.log" . $this->log . "\n";
                    echo $resultline_top;
                    $this->parseProc(true);
                } else {
                    echo "{$this->c("yellow")}Option \"Processes (Active)\" unavailable\n{$this->c()}";
                    return $this->askMethod();
                }
                break;
            case 7: // Files
                if ($this->option_available["files"]) {
                    echo "\tAnalyzing: " . $this->target_dir . "/overload.files.log" . $this->log . "\n";
                    echo $resultline_top;
                    echo "Not yet implemented.";
                } else {
                    echo "{$this->c("yellow")}Option \"Files\" unavailable\n{$this->c()}";
                    return $this->askMethod();
                }
                break;
        }
        echo "\n{$this->c("urgent")} ------------------------- End of results -------------------------- {$this->c()}\n\n";
        
        return $this->askMethod();
    }

    // Header display
    public function showLogs($display_banner = true) {
        $this->clear();
        
        $display_version = str_pad("Version: " . VERSION, 24, " ", STR_PAD_BOTH);
        $display_updated = str_pad("Updated: " . UPDATED, 24, " ", STR_PAD_BOTH);
        
        if ($display_banner) {
        
            $banner = "";
            $block_character = [
                "#" => chr(226) . chr(150) . chr(136),
                "="   => chr(226) . chr(150) . chr(128),
                "_" => chr(226) . chr(150) . chr(132)
            ];
        
            switch ($this->getScanType()) {
                case "syssnap":
                    $banner = <<< EOF
=======================================================
 _===_ ##   # _==_ #==_   _===_ ==#== _==_ ==#== _===_
 #     # #  # #  # #  #   #       #   #  #   #   #    
  ===_ # =_ # #==# #==     ===_   #   #==#   #    ===_
     # #  # # #  # #          #   #   #  #   #       #
 =___= #   ## #  # #      =___=   #   #  #   #   =___=
_______________________________________________________
# {$display_version} # {$display_updated} #
=======================================================
EOF;
                    break;
            
                case "axon":
                    $banner = <<< EOF
=======================================================
 _==_ #   # _==_ ##   #   _===_ ==#== _==_ ==#== _===_
 #  #  # #  #  # # #  #   #       #   #  #   #   #    
 #==#   #   #  # # =_ #    ===_   #   #==#   #    ===_
 #  #  # #  #  # #  # #       #   #   #  #   #       #
 #  # #   # =__= #   ##   =___=   #   #  #   #   =___=
_______________________________________________________
# {$display_version} # {$display_updated} #
=======================================================
EOF;
                break;
            }
            
            echo str_replace(array_keys($block_character), array_values($block_character), $banner);
        }
        

        echo "\n\n";
        if ($this->debug_level) echo "{$this->c("bad")}DEBUG MODE{$this->c()}\n";
        echo "Target Directory: " . $this->target_dir . "\n";
        echo $this->c($this->allow_shell_exec ? "green" : "gray") . "shell_exec " . ($this->allow_shell_exec ? "enabled" : "disabled") . "{$this->c()}\n";

        echo "{$this->c("white")}Axon Log Dates:\n";
        echo "{$this->c("yellow")}-- Current date: " . date("r") . "{$this->c()}\n";
        
        // Report that there are no logs if there aren't any
        if (!count($this->log_db)) {
            if ($this->logs_were_found) {
                echo "\n{$this->c("green")}No overloads found in " . $this->getScanType() . " logs.{$this->c()}\n";
                echo " - Peak load: " . $this->peak_load . " out of " . $this->nproc() . " threads\n";
                echo " - Use --force to display all logs\n";
                if ($this->allow_shell_exec) {
                    echo " - Use --now to display current information\n";
                } else {
                    echo " - Enable shell_exec to grant use of --now to display current information\n";
                }
                die;
            } else {
                echo "{$this->c("pink")}No " . $this->getScanType() . " logs found in {$this->target_dir}";
            }
        }
        
        // End the script here if shell_exec isn"t available and there are no axon logs
        if (!$this->allow_shell_exec && count($this->log_db) == 0) {
            echo "{$this->c("pink")} Additionally, shell_exec is disabled so \"Current Status\" is not available.{$this->c()}\n";
            die;
        }
        echo "{$this->c()}\n";
        
        switch ($this->getScanType()) {
            case "axon":
                $this->showAxonLogs();
                break;
                
            case "syssnap":
                $this->showSysSnapLogs();
                break;
        }
    }
    
    public function showAxonLogs() {
        $now_key = 1;
        foreach ($this->log_db as $key=>$unix) {
            $unixtime = $unix;
            $now_key++;
            if ($unix == -1) {
                echo ($key + 1) . ". No Stamp       -- Age: ";
                $unix = "";
            } else {
                echo ($key + 1) . ". $unix     -- Age: ";
                $unix = ".$unix";
            }
            
            if ($unix == "") {
                $start_date = new DateTime(date("r", filemtime($this->target_dir."/overload.net.log")));
            } else {
                $start_date = new DateTime(date("r", str_replace(".", "", $unix)));
            }
            $since_start = $start_date->diff(new DateTime(date("r")));
            
            $fresh = -1;
            
            // [FRESHNESS] 5 - Now  - Less than 1 minute
            // [FRESHNESS] 4 - New  - Less than 1 minute
            // [FRESHNESS] 3 - High - Less than 1 minute
            // [FRESHNESS] 2 - Med  - Less than 1 minute
            // [FRESHNESS] 1 - Low  - Less than 1 minute
            // [FRESHNESS] 0 - None - No longer fresh
            if (
                $fresh == -1 &&
                (
                    ($since_start->y == 0) &&
                    ($since_start->m == 0) &&
                    ($since_start->d == 0) &&
                    ($since_start->h == 0) &&
                    ($since_start->i == 0)
                )
            ) {
                $fresh = 5;
            }
            if (
                $fresh == -1 &&
                (
                    ($since_start->y == 0) &&
                    ($since_start->m == 0) &&
                    ($since_start->d == 0)
                )
            ) {
                if ($since_start->h < 1)
                    $fresh = 4;
                else if ($since_start->h < 6)
                    $fresh = 3;
                else
                    $fresh = 2;
            }
            
            if (($fresh == -1) && (($since_start->y == 0) && ($since_start->m == 0)))
                if ($since_start->d < 2)
                    $fresh = 2;
                else if ($since_start->d < 3)
                    $fresh = 1;
                else
                    $fresh = 0;
            
            if (($fresh == -1) && (($since_start->m > 0) || ($since_start->y > 0)))
                $fresh = 1;
            
            if ($fresh == -1)
                $fresh = 0;
            
            echo $this->c("gray");
            
            // White display on year
            if ($since_start->y > 0) { echo $this->c("white"); }
            echo str_pad($since_start->y, 2, 0, STR_PAD_LEFT)."y ";
            
            // White display on month
            if ($since_start->m > 0) { echo $this->c("white"); }
            echo str_pad($since_start->m, 2, 0, STR_PAD_LEFT)."mo ";
            
            // White display on day
            if ($since_start->d > 0) { echo $this->c("white"); }
            echo str_pad($since_start->d, 2, 0, STR_PAD_LEFT)."d ";
            
            // White display on hour
            if (($since_start->h > 0) && ($since_start->d == 0)) { echo $this->c("white"); }
            echo str_pad($since_start->h, 2, 0, STR_PAD_LEFT)."h ";
            
            // White display on minutes
            if ($since_start->i > 0) { echo $this->c("white"); }
            echo str_pad($since_start->i, 2, 0, STR_PAD_LEFT)."m ";
            
            // White display on seconds
            if ($since_start->s > 0) { echo $this->c("white"); }
            echo str_pad($since_start->s, 2, 0, STR_PAD_LEFT)."s ";
            
            echo "{$this->c()}-- Date: {$this->c("magenta")}";
            echo is_int(intval($unixtime)) && $unixtime != -1 ? date("M d Y @ H:i:s", $unixtime) : "                      ";
            echo $this->c();
            
            if ((file_exists($this->target_dir . "/overload.http.log" . $unix)) && strlen(file_get_contents($this->target_dir . "/overload.http.log" . $unix)) > 0) {
                $handle_sl = fopen($this->target_dir . "/overload.http.log" . $unix, "r");
                $contents_http = fread($handle_sl, filesize($this->target_dir . "/overload.http.log" . $unix));
                fclose($handle_sl);
                preg_match("/load\: [0-9\.\s]*/", $contents_http, $matches);
                if (isset($matches[0])) {
                    echo " -- " . str_replace("Load:", "Load:{$this->c("pink")}", trim(ucfirst($matches[0]))) . "{$this->c()}";
                } else {
                    echo " -- Load: {$this->c("yellow")}???{$this->c()}";
                }
            } else {
                echo " -- Load: {$this->c("yellow")}???{$this->c()}";
            }
            
            $exists = [
                "exim" => true,
                "files" => true,
                "http" => true,
                "mysql" => true,
                "net" => true,
                "procs" => true
            ];
            
            if (!file_exists($this->target_dir . "/overload.exim.log" . $unix))
                $exists["exim"] = false;
            if (!file_exists($this->target_dir . "/overload.files.log" . $unix))
                $exists["files"] = false;
            if (!file_exists($this->target_dir . "/overload.http.log" . $unix))
                $exists["http"] = false;
            if (!file_exists($this->target_dir . "/overload.mysql.log" . $unix))
                $exists["mysql"] = false;
            if (!file_exists($this->target_dir . "/overload.net.log" . $unix))
                $exists["net"] = false;
            if (!file_exists($this->target_dir . "/overload.procs.log" . $unix))
                $exists["procs"] = false;
            
            if (in_array(false, $exists)) {
                echo "\n{$this->c("yellow")} \_ Warning! Missing logs: {$this->c()}";
                $missing_count = 0;
                foreach ($exists as $key=>$e) {
                    if (!$e) {
                        echo $this->c("red") . ($missing_count > 0 ? ", " : "") . $key . $this->c();
                        $missing_count++;
                    }
                }
            }
            echo $this->c()."\n";
        }
        
        // Allow "Current Status" as an option if shell_exec is available
        if ($this->allow_shell_exec)
            echo "{$now_key}. Current Status -- Age: " . $this->c("gray") . "00y 00mo 00d 00h 00m 00s" . $this->c() . " -- Date: " . $this->c("magenta") . date("M d Y @ H:i:s") . $this->c() . "\n";
    }
    
    public function getLoadThresholdColor($value) {
        if ($value > $this->overload_danger_threshold) {
            return "bad";
        } else if ($value > $this->overload_warning_threshold) {
            return "yellow";
        }
        
        return "";
    }
    
    public function getPaddedLoad($load) {
        return $load === floatval(0) ? "0.00" : str_pad($load, 4, "0", STR_PAD_RIGHT);
    }
    
    public function showSysSnapLogs() {
        $prev_date = false;
		$prev_hour = false;
		$log_count = count($this->log_db);
        $padding = strlen(strval($log_count));
		$newline_tick = 0;
		
        foreach($this->log_db as $key=>$log) {
            $current_date = date("D, d M Y", $log["unix"]);
			$current_hour = date("H", strtotime($current_date . " " . $log["time"]));
			
            if ($prev_date != $current_date) {
                if ($prev_date !== false) echo "\n";
                echo "== $current_date ==\n";
                $prev_date = $current_date;
            }
			
			if ($log_count >= 300) {
				if ($prev_hour != $current_hour) {
					if ($prev_hour !== false) {
						if ($newline_tick) echo "\n";
						echo "\n";
						$newline_tick = 0;
					}
					echo "-- $current_hour:00 --\n";
					$prev_hour = $current_hour;
				}
			
				$display_date = date("H:i", strtotime($current_date . " " . $log["time"]));
				echo $this->c("gray") . str_pad($key + 1, $padding, " ", STR_PAD_LEFT) . ". " . $this->c() . $display_date;
				echo " - Load: " . $this->scale($log["load"]["1min"], ($this->nproc() / 6), 0, 4, "", STR_PAD_RIGHT);
				
				$newline_tick++;
				if ($newline_tick >= 5) {
					echo "\n";
					$newline_tick = 0;
				} else {
					echo "     ";
				}
			} else {
				$display_date = date("r", strtotime($current_date . " " . $log["time"]));
				echo str_pad($key + 1, $padding, " ", STR_PAD_LEFT) . ". " . $display_date;
				echo " -- Load:";
				
				if ($this->force_log_display) {
					echo " " . $this->scale($log["load"]["1min"], ($this->nproc() / 6), 0, 4, "", STR_PAD_RIGHT);
				} else {
					echo " " . $this->c($this->getLoadThresholdColor($log["load"]["1min"])) . $this->getPaddedLoad($log["load"]["1min"]) . $this->c();
					echo " " . $this->c($this->getLoadThresholdColor($log["load"]["5min"])) . $this->getPaddedLoad($log["load"]["5min"]) . $this->c();
					echo " " . $this->c($this->getLoadThresholdColor($log["load"]["10min"])) . $this->getPaddedLoad($log["load"]["10min"]) . $this->c();
				}
				echo "\n";
			}
        }
		
		echo "\n";
    }
    
    // Used in log parsing to sort by unix date
    private function sortByUnix($a, $b) {
        return $a["unix"] > $b["unix"];
    }
    
    // Used in net and processes to sort for "total" key
    private function sortByTotal($a, $b) {
        return $b["total"] - $a["total"];
    }
    
    // Used in process parsing to sort for cpu usage
    private function sortByCPU($a, $b) {
        return $b["cpu"] - $a["cpu"];
    }
    
    // Used in process parsing to sort for mem usage
    private function sortByMem($a, $b) {
        return $b["mem"] - $a["mem"];
    }
    
    public function getLogTime() {
        if ($this->show_current_information)
            return date("r");
        
        return date("r", isset($this->log["unix"]) ? $this->log["unix"] : $this->log_unix);;
    }
    
    /* Exim */
    public function parseExim($content = "") {
        switch (true) {
            case ($content != ""):
                $log_exim = $content;
                break;
            
            case ($this->log_unix != "now"):
                $handle_exim = fopen("{$this->target_dir}/overload.exim.log{$this->log}", "r");
                $log_exim = fread($handle_exim, filesize("{$this->target_dir}/overload.exim.log{$this->log}"));
                fclose($handle_exim);
                break;
                
            default:
                $log_exim = shell_exec("exim -bp");
                break;
        }
        $log_exim_lines = explode("\n", $log_exim);

        echo "{$this->c("green")}>> Exim Queue{$this->c()}\n";

        $emails = count(explode("<", $log_exim)) - 1;
        echo "Emails:\t\t".$this->scale($emails, [500, 1500, 5000, 10000, 50000])."\n";

        $frozen_emails = count(explode("*** frozen ***", $log_exim)) - 1;
        echo "Frozen emails:\t".$this->scale($frozen_emails, 500)."\n";

        $root_emails = count(explode("<root@", $log_exim)) - 1;
        echo "Root emails:\t".$this->scale($root_emails, 500)."\n";

        $top_emails = [];
        foreach ($log_exim_lines as $k=>$line) {
            if ($k % 3 == 1) {
                if (isset($top_emails[trim($line)])) {
                    $top_emails[trim($line)]++;
                } else {
                    $top_emails[trim($line)] = 1;
                }
            }
        }
        asort($top_emails);
        $top_emails = array_reverse($top_emails);

        echo "\n{$this->c("magenta")}== Top 5 Email Addresses =========={$this->c()}\n";
        $c = 0;
        foreach ($top_emails as $k=>$user_process_count) {
            if ($c < 5 && trim($k) != "") {
                echo $this->scale($user_process_count, 100, 0, 5) . " " . $k . "\n";
                $c++;
            }
        }

        echo "\n{$this->c("magenta")}== Users with 10+ emails in queue ={$this->c()}\n";
        foreach ($top_emails as $k=>$user_process_count) {
            if ($user_process_count > 10 && trim($k) != "") {
                echo $this->scale($user_process_count, 100, 0, 5) . " " . $k . "\n";
            }
        }

        echo "\n";
    }
    
    public function parseToVal($input) {
        if (preg_match("/^\-?[0-9]+$/", $input)) {
            return intval($input);
        } else if (preg_match("/^\-?[0-9]+\.[0-9]+$/", $input)) {
            return floatval($input);
        }
        
        return $input;
    }
    
    /* MySQL */
    public function parseMySQL($content = "") {
        switch (true) {
            case (!empty($content)):
                $log_mysql = $content;
                unset($content);
                break;
                
            case ($this->show_current_information):
                $log_mysql = shell_exec("mysqladmin proc stat");
                break;
            
            case (!empty($this->log_unix)):
                $handle_mysql = fopen($this->target_dir."/overload.mysql.log{$this->log}", "r");
                $log_mysql = fread($handle_mysql, filesize($this->target_dir."/overload.mysql.log{$this->log}"));
                fclose($handle_mysql);
                break;
                
            default:
                $this->askMethod();
                break;
        }
        $log_mysql_lines = explode("\n", $log_mysql);
        
        echo "{$this->c("green")}>> MySQL{$this->c()}\n";

        // Output general information line
        if (strpos($log_mysql, "Uptime:") !== false)
            echo preg_replace("/([0-9\.]+)/", "{$this->c("cyan")}\${1}{$this->c()}", $log_mysql_lines[count($log_mysql_lines) - 2])."\n";

        $db_query_totals = [];
        $user_query_totals = [];
        $query_overview = [];
        $header_guide = [];
        foreach ($log_mysql_lines as $line) {
            if (strlen($line) > 0 && preg_match("/^\| .* \|$/", $line)) {
                $proc_line = preg_replace("/^\|| \|$/", "", $line);
                $proc_line_split = array_map("trim", explode(" | ", $proc_line));
                
                // If it's the header line, dump the proc table headers into a table for use later
                if (strtolower($proc_line_split[0]) == "id") {
                    foreach ($proc_line_split as $index=>$key)
                        $header_guide[$index] = strtolower($key);
                        
                // If it's a process line, save the values according to the stored headers
                } else {
                    $current_sql_process = [];
                    foreach ($proc_line_split as $index=>$val)
                        $current_sql_process[$header_guide[$index]] = $this->parseToVal($val);
                    
                    $query_user = $current_sql_process["user"];
                    $query_db = $current_sql_process["db"];
                    $query_cmd = $current_sql_process["command"];
                    $query_time = $current_sql_process["time"];
                    
                    if (!empty($query_db)) {
                        if (isset($user_query_totals[$query_user])) {
                            $user_query_totals[$query_user]++;
                        } else {
                            $user_query_totals[$query_user] = 1;
                        }
                        
                        if (isset($db_query_totals[$query_db])) {
                            $db_query_totals[$query_db]++;
                        } else {
                            $db_query_totals[$query_db] = 1;
                        }
                        
                        if (!isset($query_overview[$query_db])) {
                            $query_overview[$query_db] = [];
                            $query_overview[$query_db]["time"] = 0;
                        }
                        
                        if (isset($query_overview[$query_db])) {
                            if (isset($query_overview[$query_db]["commands"][strtolower($query_cmd)])) {
                                $query_overview[$query_db]["commands"][strtolower($query_cmd)]++;
                            } else {
                                $query_overview[$query_db]["commands"][strtolower($query_cmd)] = 1;
                            }
                            $query_overview[$query_db]["time"] += intval($query_time);
                        }
                    }
                }
            }
        }
        
        // var_dump($query_overview);
        // die;
        
        // Sort the commands from most occurring to least
        ksort($query_overview);
        foreach ($query_overview as $db=>&$db_info) {
            ksort($db_info["commands"]);
            $db_info["commands"] = array_reverse($db_info["commands"]);
        }
        
        // Sort user totals from largest to smallest
        asort($db_query_totals);
        $db_query_totals = array_reverse($db_query_totals);
        asort($user_query_totals);
        $user_query_totals = array_reverse($user_query_totals);
        
        // Remove spare empty array
        if (isset($db_query_totals [""]))
            unset($db_query_totals [""]);
        if (isset($user_query_totals[""]))
            unset($user_query_totals[""]);
        
        // var_dump("User Query Totals", $user_query_totals, "Query Overview", $query_overview);
        // die;
        
        echo "\n{$this->c("magenta")}== Top 5 MySQL Databases =========={$this->c()}\n";
        echo "Queries  Database\n";
        $top_sql_dbs_display_limit = 5;
        foreach ($db_query_totals as $sql_database=>$user_process_count) {
            if ($top_sql_dbs_display_limit > 0) {
                // echo "{$this->c("cyan")}".str_pad($user_process_count, 7, " ", STR_PAD_LEFT);
                echo $this->scale($user_process_count, 1, 0, 7, " ");
                // echo "{$this->c("cyan")}".str_pad($user_process_count, 7, " ", STR_PAD_LEFT);
                echo "{$this->c()}  " . str_pad($sql_database. " ", 65, "-", STR_PAD_RIGHT);
                // echo "-- Total MySQL Time: {$this->c("cyan")}".str_pad($query_overview[$k]["time"], 8, " ", STR_PAD_RIGHT)."{$this->c()}";
                echo "-- Total MySQL Time:" . $this->scale($query_overview[$sql_database]["time"], 15, 0, 8, " ", STR_PAD_RIGHT);
                echo " --> Query Types [ ";
                $first_type = true;
                
                foreach ($query_overview[$sql_database]["commands"] as $query_type=>$query_count) {
                    if ($query_type == "query")
                        $query_type = "{$this->c("yellow")}$query_type";
                    
                    if ($first_type) {
                        echo "$query_type:{$this->c()} {$this->c("cyan")}$query_count{$this->c()}";
                        $first_type = false;
                    } else {
                        echo ", $query_type:{$this->c()} {$this->c("cyan")}$query_count{$this->c()}";
                    }
                }
                echo " ]\n";
                $top_sql_dbs_display_limit--;
            }
        }

        echo "\n{$this->c("magenta")}== Users with 5+ queries ====={$this->c()}\n";
        echo "Queries  User\n";
        foreach ($user_query_totals as $sql_username=>$user_process_count) {
            if ($user_process_count > 5) {
                echo "{$this->c("cyan")}" . str_pad($user_process_count, 7, " ", STR_PAD_LEFT);
                echo "{$this->c()}  " . str_pad($sql_username . " ", 65, "-", STR_PAD_RIGHT);
                echo "-- Total MySQL Time: {$this->c("cyan")}" . str_pad($query_overview[$sql_username]["time"], 8, " ", STR_PAD_RIGHT)."{$this->c()}";
                echo " --> Query Types [ ";
                $first_type = true;
                
                asort($query_overview[$sql_username]);
                
                foreach ($query_overview[$sql_username]["commands"] as $query_type=>$query_count) {
                    if ($query_type == "query")
                        $query_type = "{$this->c("yellow")}$query_type";
                    
                    if ($first_type) {
                        echo "$query_type:{$this->c()} {$this->c("cyan")}$query_count{$this->c()}";
                        $first_type = false;
                    } else {
                        echo ", $query_type:{$this->c()} {$this->c("cyan")}$query_count{$this->c()}";
                    }
                }
                echo " ]\n";
            }
        }
        
        echo "\n";
    }

    /* Processes */
    public function parseProc($active_processes_only = false, $content = "") {
        // Open the procs Axon log and split it into different lines
        switch (true) {
            case (!empty($content)):
                $proc_log = $content;
                unset($content);
                break;
                
            case ($this->show_current_information):
                $proc_log = shell_exec("ps awwxf -o user:20,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,args");
                break;
                
            case (!empty($this->log_unix)):
                $proc_log = file_get_contents($this->target_dir . "/overload.procs.log{$this->log}");
                break;
                
            default:
                return $this->askMethod();
                break;
        }
		
		// Cut down ps output to better handle months
		$proc_log = preg_replace_callback(
			"/(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9]+)/i",
			function($m) {
				return count($m) > 1 ? $m[1].$m[2] : $m[0];
			},
			$proc_log
		);
		
        // Prevent starting day from making splits uneven
        $proc_log_lines = explode("\n", $proc_log);
        
        // Count the total number of processes
        $proc_count = count($proc_log_lines) - 1;
        
        echo "{$this->c("green")}>> Processes" . ($active_processes_only ? " (Active)" : "") . "{$this->c()}\n";

        // Create array for counting how many processes a user is running
        $users_overview = [];

        // Processes with over 10% usage
        $total_cpu_usage = 0;
        $total_mem_usage = 0;
        $total_vsz_usage = 0;
        $total_vsz_unit = "KiB";
        $total_rss_usage = 0;
        $total_rss_unit = "KiB";
        $instant_load = 0;
        $wait_on_cpu = 0;
        $wait_on_disk = 0;
        
        $processes_filtered = [];
        foreach ($proc_log_lines as $linenum=>$line) {
            if (!empty(trim($line))) {
                // Split process line in a fashion similar to awk
                $split_process = explode(" ", preg_replace("/\s+/", " ", $line));
                $process_id = intval($split_process[1]);
                
                // Check if the process is an active process
                
                /* PROCESS STATE CODES:
                 *
                 * R running or runnable (on run queue)
                 * D Active sleep (usually IO)
                 * S interruptible sleep (waiting for an event to complete)
                 * Z defunct/zombie, terminated but not reaped by its parent
                 * T stopped, either by a job control signal or because it is being traced
                 */
                $is_active_process = preg_match("/^[RD]/", trim($split_process[7]));
                if ($is_active_process) $instant_load++; // Increase instantaneous load value
                
                if (!$active_processes_only || ($active_processes_only && $is_active_process)) {
                    // Make an array for that process
                    $processes_filtered[$process_id] = [];
                    $processes_filtered[$process_id]["line"] = $linenum;
                    $processes_filtered[$process_id]["user"] = $split_process[0];
                    $processes_filtered[$process_id]["cpu"] = floatval($split_process[2]);    // It is the CPU time used divided by the time the process has been running. 
                    $total_cpu_usage += $processes_filtered[$process_id]["cpu"];
                    $processes_filtered[$process_id]["mem"] = floatval($split_process[3]);    // ratio of the process’s resident set size to the physical memory on the machine 
                    $total_mem_usage += $processes_filtered[$process_id]["mem"];
                    $processes_filtered[$process_id]["vsz"] = intval($split_process[4]);    // virtual memory usage of entire process (in KiB)
                    $total_vsz_usage += $processes_filtered[$process_id]["vsz"];
                    $processes_filtered[$process_id]["rss"] = intval($split_process[5]);    // resident set size, the non-swapped physical memory that a task has used (in KiB)
                    $total_rss_usage += $processes_filtered[$process_id]["rss"];
                    $processes_filtered[$process_id]["tty"] = $split_process[6];    // controlling tty (terminal)
                    
                    $processes_filtered[$process_id]["state"] = $split_process[7]; // multi-character process state
                    if (isset($split_process[7][0]) && $split_process[7][0] == "R") $wait_on_cpu++; // increase the number of processes waiting on CPU
                    if (isset($split_process[7][0]) && $split_process[7][0] == "D") $wait_on_disk++; // increase the number of processes waiting on disk
                    $processes_filtered[$process_id]["active"] = $is_active_process; // multi-character process state 
                    
                    $processes_filtered[$process_id]["start"] = $split_process[8]; // starting time or date of the process 
                    $processes_filtered[$process_id]["cputime"] = $split_process[9]; // cumulative CPU time
                    
                    // Increase the number of processes a user is running
                    $is_uid = preg_match("/^[0-9]+$/", $split_process[0]);
                    $get_username = $is_uid ? posix_getpwuid($split_process[0])["name"] : $split_process[0]; // Change process running under "numbered" users to use an actual username
                    if (!isset($users_overview[$get_username])) {
                        $users_overview[$get_username] = 1;
                    } else {
                        $users_overview[$get_username]++;
                    }
                    
                    // Remove the already stored lines and pull the remainder of the command
                    for ($i = 0; $i < 10; $i++) { array_splice($split_process, 0, 1); }
                    $process_command = implode(" ", $split_process);
                    $no_parent_command = trim(preg_replace("/(\\\_)*|\|*/", "", $process_command));
                    $processes_filtered[$process_id]["cmd"] = $no_parent_command; // command with all its arguments 
                    $no_parent_command_split = explode(" ", $no_parent_command);
                    $processes_filtered[$process_id]["proc"] = $no_parent_command_split[0];
                    $processes_filtered[$process_id]["parent"] = -1;
                    
                }
                
                // var_dump($processes_filtered);
            }
        }
        
        // Sort the user overview
        asort($users_overview);
        $users_overview = array_reverse($users_overview);
        
        /* 
         * ===========================================
         * == OUTPUT
         * ===========================================
         */
        
        $proc_count = ($proc_count > 500 ? $this->c("pink") . $proc_count . $this->c() : $proc_count);
        echo "Total Process Count:\t$proc_count\n";
        $total_cpu_usage = ($total_cpu_usage > 100 ? $this->c("pink") . $total_cpu_usage . "%" . $this->c() : $total_cpu_usage . "%");
        echo "Total CPU Usage:\t$total_cpu_usage\n";
        $total_mem_usage = ($total_mem_usage > 100 ? $this->c("pink") . $total_mem_usage . "%" . $this->c() : $total_mem_usage . "%");
        echo "Total Memory Usage:\t$total_mem_usage\n";
        
        // Instantaneous load
        $cpus = $this->nproc();
        switch ($instant_load) {
            case ($instant_load > ($cpus * 1.5)):
                $instant_load = $this->c("bad") . $instant_load . $this->c();
                break;
            case ($instant_load > $cpus):
                $instant_load = $this->c("red") . $instant_load . $this->c();
                break;
            case ($instant_load > ($cpus * 0.75)):
                $instant_load = $this->c("yellow") . $instant_load . $this->c();
                break;
        }
        
        echo "Instantaneous Load:\t$instant_load (out of $cpus threads)\n";
        if ($active_processes_only) {
            echo "Waiting on CPU:\t\t" . $this->scale($wait_on_cpu, 2, $cpus) . "\n";
            echo "Waiting on disk:\t" . $this->scale($wait_on_disk, 2, $cpus) . "\n";
        }

        echo "\n{$this->c("magenta")}== Top 5 Users ======================{$this->c()}\n";
        // foreach ($users_overview as $k=>$user_process_count) {
        $c = 0;
        foreach ($users_overview as $k=>$user_process_count) {
            if ($c < 5) {
                if ($k === "root") {
                    echo $this->scale($user_process_count, 100, 0, 4) . " {$this->c("underline")}" . $k . "{$this->c()}\n";
                } else {
                    echo $this->scale($user_process_count, 10, 0, 4) . " " . $k . "\n";
                }
                $c++;
            }
        }

        $proc_threshold = ($active_processes_only ? 10 : 20);
        echo "\n{$this->c("magenta")}== Users with {$proc_threshold}+ processes ========={$this->c()}\n";
        foreach ($users_overview as $k=>$user_process_count) {
            if ($user_process_count > $proc_threshold) {
                switch ($k) {
                    case "root":
                        echo $this->scale($user_process_count, 100, 0, 4) . " {$this->c("underline")}" . $k . "{$this->c()}\n";
                        break;
                    default:
                        echo $this->scale($user_process_count, 10, 0, 4) . " " . $k . "\n";
                        break;
                }
            }
        }
        
        // Process commands into a layout
        $command_overview = [];
        foreach ($processes_filtered as $pid=>$process) {
            // Shorten the command if necessary
            $max_length = ($this->allow_shell_exec ? intval(shell_exec("tput cols")) - 42 : 150);
            if (strlen($process["cmd"]) > $max_length) $process["cmd"] = substr($process["cmd"], 0, $max_length) . "...";
            
            // Compile stats for each command
            if (!isset($command_overview[$process["cmd"]]))
                $command_overview[$process["cmd"]] = [
                    "total" => 0,
                    "cpu" => 0,
                    "mem" => 0
                ];
            
            $command_overview[$process["cmd"]]["total"]++;
            $command_overview[$process["cmd"]]["cpu"] += $process["cpu"];
            $command_overview[$process["cmd"]]["mem"] += $process["mem"];
        }
        uasort($command_overview, [$this, "sortByTotal"]);
        echo "\n{$this->c("magenta")}== Top 5 Most Common Commands ======={$this->c()}\n";
        $c = 0;
        foreach ($command_overview as $cmd=>$procinfo) {
            if ($c < 5) {
                echo $this->scale($procinfo["total"], 30, 30, 4) . " -- " . $this->scale($procinfo["cpu"], 20, 0, 6, "%") . " CPU -- " . $this->scale($procinfo["mem"], 20, 0, 6, "%") . " MEM -- $cmd\n";
                $c++;
            }
        }
        
        $command_overview_cpu = $command_overview;
        uasort($command_overview_cpu, [$this, "sortByCPU"]);
        echo "\n{$this->c("magenta")}== Top 5 Commands by CPU ============{$this->c()}\n";
        $c = 0;
        foreach ($command_overview_cpu as $cmd=>$procinfo) {
            if ($c < 5) {
                echo $this->scale($procinfo["total"], 30, 30, 4) . " -- " . $this->scale($procinfo["cpu"], 20, 0, 6, "%") . " CPU -- " . $this->scale($procinfo["mem"], 20, 0, 6, "%") . " MEM -- $cmd\n";
                $c++;
            }
        }
        
        $command_overview_mem = $command_overview;
        uasort($command_overview_mem, [$this, "sortByMem"]);
        echo "\n{$this->c("magenta")}== Top 5 Commands by Memory ========{$this->c()}\n";
        $c = 0;
        foreach ($command_overview_mem as $cmd=>$procinfo) {
            if ($c < 5) {
                echo $this->scale($procinfo["total"], 30, 30, 4) . " -- " . $this->scale($procinfo["cpu"], 20, 0, 6, "%") . " CPU -- " . $this->scale($procinfo["mem"], 20, 0, 6, "%") . " MEM -- $cmd\n";
                $c++;
            }
        }
        
        echo "\n";
    }

    /* Net */
    public function parseNetTCP($level = 1, $content = "") {
        switch (true) {
            case (!empty($content)):
                $net_log = $content;
                unset($content);
                break;
            
            case ($this->show_current_information):
                $net_log = shell_exec("netstat -anp");
                break;
                
            case (!empty($this->log_unix)):
                $net_log = file_get_contents($this->target_dir . "/overload.net.log" . $this->log);
                break;
                
            case (!empty($this->log)):
                $net_log = file_get_contents($this->log);
                break;
                
            default:
                return $this->askMethod();
                break;
        }
        
        $net_db = [];
        $ip_db = [];
        $sub_db = [];
        $server_ip = trim(gethostbyname(gethostname()));
        $net_log = preg_replace("/[\t ]+/", " ", $net_log);
        $net_log_split = explode("\n", $net_log);
        $net_log_active = preg_grep("/^(udp|tcp)/", $net_log_split);
        $busiest_port = 0;
        $busiest_ip = 0;
        $busiest_sub = 0;
        
        // var_dump($net_log_active);
        foreach ($net_log_active as $line) {
            $net_section = explode(" ", $line);
            $status = strtoupper($net_section[5]);
            switch ($status) {
                case "LISTEN":
                    $net_section[3] = preg_replace("/^.*:+/", "", $net_section[3]);
                    $net_section[6] = preg_replace("/^[0-9]+\//", "", $net_section[6]);
                    $proto = $net_section[0];
                    $port = $net_section[3];
                    $service = $net_section[6];
                    $idx = "$proto:$port";
                    
                    if ($service != "-") {
                        if (!isset($net_db[$idx])) {
                            $net_db[$idx] = [
                                "service" => $service,
                                "protocol" => $proto,
                                "port" => $port,
                                "connections" => [],
                                "total" => 0
                            ];
                        }
                    }
                    break;
                case "ESTABLISHED":
                case "SYN_RECV":
                case "FIN_WAIT2":
                case "UNKNOWN":
                    $net_section[3] = preg_replace("/^.*:(:ffff:)?/", "", $net_section[3]);
                    $net_section[4] = preg_replace("/:[0-9]+$/", "", $net_section[4]);
                    $net_section[4] = preg_replace("/^::ffff:/", "", $net_section[4]);
                    $net_section[6] = preg_replace("/^[0-9]+\//", "", $net_section[6]);
                    $proto = $net_section[0];
                    $port = $net_section[3];
                    $ip = $net_section[4];
                    $ip_sub = explode(".", $net_section[4])[0] . "." . explode(".", $net_section[4])[1] . ".0.0/16";
                    $service = $net_section[6];
                    $idx = "$proto:$port";
                    
                    if ($service != "-" && $ip != $server_ip) {
                        if (!isset($net_db[$idx])) {
                            $net_db[$idx] = [
                                "service" => $service,
                                "protocol" => $proto,
                                "port" => $port,
                                "connections" => [],
                                "total" => 0
                            ];
                        }
                        
                        // By Service/Port
                        if (!isset($net_db[$idx]["connections"][$ip])) $net_db[$idx]["connections"][$ip] = 0;
                        $net_db[$idx]["connections"][$ip]++;
                        $net_db[$idx]["total"]++;
                        if ($net_db[$idx]["total"] > $busiest_port) $busiest_port = $net_db[$idx]["total"];
                        
                        // By IP
                        if (!isset($ip_db[$ip])) $ip_db[$ip] = [
                            "services" => [],
                            "total" => 0
                        ];
                        if (!isset($ip_db[$ip]["services"][$service])) $ip_db[$ip]["services"][$service] = 0;
                        $ip_db[$ip]["services"][$service]++;
                        $ip_db[$ip]["total"]++;
                        if ($ip_db[$ip]["total"] > $busiest_ip) $busiest_ip = $ip_db[$ip]["total"];
                        
                        // By IP Subnet
                        if (!isset($sub_db[$ip_sub])) $sub_db[$ip_sub] = [
                            "services" => [],
                            "total" => 0
                        ];
                        if (!isset($sub_db[$ip_sub]["services"][$service])) $sub_db[$ip_sub]["services"][$service] = 0;
                        $sub_db[$ip_sub]["services"][$service]++;
                        $sub_db[$ip_sub]["total"]++;
                        if ($sub_db[$ip_sub]["total"] > $busiest_sub) $busiest_sub = $sub_db[$ip_sub]["total"];
                    }
                    break;
            }
        }
        unset($proto, $port, $ip, $service, $idx);
        
        // Sort by connections
        uasort($net_db, function($a, $b) {
            return $a["total"] < $b["total"];
        });
        uasort($ip_db, function($a, $b) {
            return $a["total"] < $b["total"];
        });
        uasort($sub_db, function($a, $b) {
            return $a["total"] < $b["total"];
        });
        
        echo "{$this->c("green")}>> Network{$this->c()}\n";
        
        echo "{$this->c("magenta")}== Port Overview ====={$this->c()}\n";
        
        $pad_length = strlen(max($busiest_port, $busiest_ip, $busiest_sub));
        
        $minimum_service_connections = 0;
        foreach ($net_db as $srv) {
            if ($srv["total"] > $minimum_service_connections) {
                if (intval($srv["total"]) > 0) {
                    echo " ";
                    switch (true) {
                        case ($srv["total"] >= 60):
                            echo "{$this->c("bad")}";
                            break;
                        case ($srv["total"] >= 40):
                            echo "{$this->c("pink")}";
                            break;
                        case ($srv["total"] >= 20):
                            echo "{$this->c("yellow")}";
                            break;
                        case ($srv["total"] > 0):
                            echo "{$this->c("green")}";
                            break;
                    }
                    echo str_pad(trim($srv["total"]), $pad_length, " ", STR_PAD_LEFT) . $this->c() . "  " . str_pad(trim($srv["service"]) . $this->c("gray") . ":" . $srv["port"] . $this->c(), 35, " ", STR_PAD_RIGHT);
                    
                    
                    echo " (Top: ";
                    $displayed_service_connection = 0;
                    $displayed_service_connection_limit = 3;
                    asort($srv["connections"]);
                    $srv["connections"] = array_reverse($srv["connections"]);
                    foreach ($srv["connections"] as $srvip=>$connections) {
                        if ($displayed_service_connection < $displayed_service_connection_limit) {
                            if ($displayed_service_connection > 0) echo ", ";
                            echo str_pad($srvip, 16, " ", STR_PAD_LEFT) . " -> " . str_pad($connections, 3, " ", STR_PAD_LEFT);
                        }
                        $displayed_service_connection++;
                    }
                    echo ")";
                    
                    echo "\n";
                }
            }
        }
        echo "\n Ports and services with zero connections are not displayed.\n";
        
        echo "\n{$this->c("magenta")}== IP Overview ====={$this->c()}\n";
        $ip_list_count = 0;
        $ip_list_count_max = 10;
        $total_connection_count = 0;
        
        foreach ($ip_db as $ip=>$ip_array) {
            if ($ip_list_count < $ip_list_count_max) {
                echo str_pad($ip_array["total"], $pad_length + 1, " ", STR_PAD_LEFT) . "  " . $ip . "\n";
                $ip_list_count++;
            }
            $total_connection_count += $ip_array["total"];
        }
        if ($ip_list_count >= $ip_list_count_max) {
            echo "\n ...and " . $this->scale(count($ip_db) - $ip_list_count_max, 20, 50) .
                " additional IPs for a total of " . $this->scale($total_connection_count, 30, 50) .
                " total connections\n";
        }
        
        echo "\n{$this->c("magenta")}== /16 Subnet Overview ====={$this->c()}\n";
        $ip_block_list_count = 0;
        $ip_block_list_count_max = 10;
        $ip_block_truncated_count = 0;
        
        // print_r($ip_block_collection);
        
        foreach ($sub_db as $ipb=>$ipb_array) {
            if ($ip_block_list_count < $ip_block_list_count_max) {
                echo str_pad($ipb_array["total"], $pad_length + 1, " ", STR_PAD_LEFT) . "  " . $ipb . "\n";
                $ip_block_list_count++;
            }
        }
        if ($ip_block_list_count >= $ip_block_list_count_max) {
            echo "\n ...and " . $this->scale(count($sub_db) - $ip_block_list_count_max, 15, 20) .
                " additional subnets for a total of " . $this->scale($total_connection_count, 20, 50) .
                " total connections{$this->c()}\n";
        }
    }
    
    public function parseApache($content = "") {
        $valid_mpms = [
            "prefork",
            "worker",
            "event"
        ];
        
        $scoreboard_key = [
            "_" => "Waiting for Connection",
            "S" => "Starting up",
            "R" => "Reading Request",
            "W" => "Sending Reply",
            "K" => "Keepalive (read)",
            "D" => "DNS Lookup",
            "C" => "Closing connection",
            "L" => "Logging",
            "G" => "Gracefully finishing",
            "I" => "Idle cleanup of worker",
            "." => "Open slot with no current process",
        ];
        
        $active_keys = [
            "S",
            "R",
            "W",
            "K",
            "D",
            "C",
            "L",
            "G",
            "I",
        ];
        
        switch (true) {
            case (!empty($content)):
                $apache_log = $content;
                unset($content);
                break;
                
            case ($this->show_current_information):
                $apache_log = shell_exec("lynx --dump localhost/whm-server-status");
                break;
            
            case (!empty($this->log_unix)):
                $apache_log = file_get_contents($this->target_dir . "/overload.http.log" . $this->log);
                break;
            
            case (!empty($this->log)):
                $apache_log = file_get_contents($this->log);
                break;
                
            default:
                return $this->askMethod();
                break;
        }
        
        $find_mpm = preg_match("/Server MPM: (\w+)$/m", $apache_log, $mpm_match);
        $mpm = $find_mpm ? strtolower($mpm_match[1]) : "";
        
        if (!in_array($mpm, $valid_mpms)) {
            echo "\n{$this->c("yellow")}Invalid MPM \"$mpm\" found.{$this->c()}\n";
            return $this->askMethod();
        }
        
        echo $this->c("green") . ">> Apache" . $this->c() . "\n";
        echo $this->c("magenta") . "== Scoreboard Summary =====" . $this->c() . "\n";
        
        $scoreboard_span = $this->snapClip("Apache Server Status", "Scoreboard Key:", "sim", $apache_log);
        switch ($mpm) {
            case "prefork":
                $get_scoreboard = preg_match_all("/^([_SRWKDCLGI\.]+)$/m", $scoreboard_span, $scoreboard_matches);
                break;
                
            default:
                $get_scoreboard = preg_match_all("/^[ ]*([_SRWKDCLGI\.]+)$/m", $scoreboard_span, $scoreboard_matches);
                break;
        }
        $scoreboard_split = str_split(implode("", $scoreboard_matches[1]));
        $scoreboard_size = count($scoreboard_split);
        $scoreboard_summary = array_count_values($scoreboard_split);
        asort($scoreboard_summary);
        $scoreboard_summary = array_reverse($scoreboard_summary);
        
        $pad_size = strlen($scoreboard_size);
        foreach($scoreboard_summary as $key=>$count)
            echo " " . str_pad($count, $pad_size, " ", STR_PAD_LEFT) . "/" . $scoreboard_size . " - " . $key . " " . $scoreboard_key[$key] . "\n";
        
        echo "\n";
        
        // Parse Apache Connections
        echo $this->c("magenta") . "== Top Active Sites =====" . $this->c() . "\n";
        $connection_db = [];
        $current_connection = "";
        $apache_connections = $this->snapClip("^\s+Scoreboard Key:", "^\s+_+$", "sim", $apache_log);
        
        foreach (explode("\n",$apache_connections) as $line) {
            $line = preg_replace("/\s+/", " ", trim($line));
            if (preg_match("/^\s*[0-9]+\-[0-9]+/", $line)) {
                if (!empty($current_connection)) {
                    $current_connection_split = explode(" ", $current_connection);
                    
                    $connection_info = [
                        "srv" => $current_connection_split[0],
                        "pid" => $current_connection_split[1],
                        "acc" => $current_connection_split[2],
                        "m" => $current_connection_split[3], // connection status
                        "cpu" => $current_connection_split[4],
                        "ss" => $current_connection_split[5],
                        "req" => $current_connection_split[6],
                        "dur" => $current_connection_split[7],
                        "conn" => $current_connection_split[8],
                        "child" => $current_connection_split[9],
                        "slot" => $current_connection_split[10],
                        "client" => $current_connection_split[11],
                        "protocol" => $current_connection_split[12],
                        "vhost" =>
                        preg_replace_callback(
                            "/^(.*):([0-9]+)$/",
                            function($m) {
                                // Enable port->protocol conversion
                                /* if ($m[2] == "80")
                                    return "http://" . $m[1];
                                else if ($m[2] == "443")
                                    return "https://" . $m[1];
                                else */
                                    return $m[0];
                            },
                            isset($current_connection_split[13]) ? $current_connection_split[13] : ""
                        ),
                        "request" => isset($current_connection_split[14], $current_connection_split[15]) ? $current_connection_split[14] . " " . $current_connection_split[15] : "",
                    ];
                    array_push($connection_db, $connection_info);
                }
                
                $current_connection = $line;
            } else if (!empty($current_connection)) {
                $current_connection .= " $line";
            }
        }
        
        if (empty($connection_db))
            echo "There are no active sites.\n";
        
        /* Top Active Sites */
        
        // By Vhost
        $connections_by_vhost = [];
        foreach ($connection_db as $index=>$conn) {
            if (in_array($conn["m"], $active_keys)) {
                if (!isset($connections_by_vhost[$conn["vhost"]]))
                    $connections_by_vhost[$conn["vhost"]] = [
                        "_connections" => [],
                        "_summary" => []
                    ];
                    
                if (!isset($connections_by_vhost[$conn["vhost"]]["_summary"][$conn["m"]]))
                    $connections_by_vhost[$conn["vhost"]]["_summary"][$conn["m"]] = 0;
                
                $connections_by_vhost[$conn["vhost"]]["_summary"][$conn["m"]]++;
                ksort($connections_by_vhost[$conn["vhost"]]["_summary"]);
                
                array_push($connections_by_vhost[$conn["vhost"]]["_connections"], $conn);
            }
        }
        
        uasort($connections_by_vhost, function($a, $b) {
            return count($a["_connections"]) > count($b["_connections"]);
        });
        
        $i = 1;
        foreach ($connections_by_vhost as $vhost=>$connections) {
            if ($i < 10) {
                echo $i . ". " . $vhost . "\n";
                foreach ($connections["_summary"] as $status=>$count)
                    echo "    \_ " . strtolower($scoreboard_key[$status]) . ": {$count}\n";
                $i++;
            }
        }
        
        echo "\n";
    }
    
    public function parseSnapProc($active_processes_only = false) {
        $proc_content = $this->snapClip("^Process List:", "^Network Connections:");
        $this->parseProc($active_processes_only, $proc_content);
    }
    
    public function parseSnapNetTCP() {
        $tcp_content = $this->snapClip("^Network Connections:", "^Active UNIX domain sockets");
        $this->parseNetTCP(1, $tcp_content);
    }
    
    public function parseSnapMySQL() {
        $sql_content = $this->snapClip("^MYSQL Processes:", "^Apache Processes:");
        
        // Sometimes, SQL doesn't log anything.
        if (empty($sql_content))
            $sql_content = <<< EOF
+---------+------------------+-----------+-------------------+---------+------+-------+------------------+----------+
| Id      | User             | Host      | db                | Command | Time | State | Info             | Progress |
+---------+------------------+-----------+-------------------+---------+------+-------+------------------+----------+
+---------+------------------+-----------+-------------------+---------+------+-------+------------------+----------+
EOF;
        
        $this->parseMySQL($sql_content);
    }
    
    public function parseSnapApache() {
        $apache_content = $this->snapClip("Apache Processes:", "$", "si");
        $this->parseApache($apache_content);
    }
    
    public function snapClip($start, $end, $flags = "sim", $content = "") {
        if (!isset($this->log["fullpath"]) && empty($content))
            return "";
        
        $load_syssnap = isset($this->log["fullpath"]) ? file_get_contents($this->log["fullpath"]) : $content;
        $get_process_chunk = preg_match_all("/" . $start . "(.*)" . $end . "/" . $flags, $load_syssnap, $get_process_chunk_matches);
        
        return isset($get_process_chunk_matches[1]) && isset($get_process_chunk_matches[1][0]) ? trim($get_process_chunk_matches[1][0]) : "";
    }
}

/* End of SnapParse Class */
/* End of SnapParse Class */
/* End of SnapParse Class */
/* End of SnapParse Class */

$parse = new SnapParse(!empty($argv) ? $argv : []);

// EOF
