diff --git a/.env.tmux.example b/.env.tmux.example new file mode 100644 index 000000000..159deb41f --- /dev/null +++ b/.env.tmux.example @@ -0,0 +1,30 @@ +# ============================================================================= +# Tmux Configuration +# ============================================================================= +# These settings configure the tmux session management system. +# Most settings have sensible defaults and don't need to be changed. + +# Tmux session name (default: from database setting or 'nntmux') +# TMUX_SESSION_NAME=nntmux + +# Process niceness level (0-19, lower = higher priority, default: 2) +# Higher values use less CPU priority +# TMUX_NICENESS=2 + +# Monitor refresh delay in seconds (default: 10) +# How often the monitor updates statistics +# TMUX_MONITOR_DELAY=10 + +# Monitor statistics refresh interval in seconds (default: 60) +# How often to re-query the database for statistics +# TMUX_REFRESH_INTERVAL=60 + +# Timeout for tmux operations in seconds (default: 300) +# TMUX_TIMEOUT=300 + +# Custom tmux config file path (default: config/tmux.conf) +# TMUX_CONFIG_FILE=/path/to/custom/tmux.conf + +# Terminal type (default: xterm-256color) +# TMUX_TERMINAL=xterm-256color + diff --git a/app/Console/Commands/TmuxAttach.php b/app/Console/Commands/TmuxAttach.php new file mode 100644 index 000000000..bfb2bd9b1 --- /dev/null +++ b/app/Console/Commands/TmuxAttach.php @@ -0,0 +1,52 @@ +option('session') + ?? Settings::settingValue('tmux_session') + ?? config('tmux.session.default_name', 'nntmux'); + + $sessionManager = new TmuxSessionManager($sessionName); + + if (! $sessionManager->sessionExists()) { + $this->error("❌ Session '{$sessionName}' does not exist"); + $this->info("💡 Run 'php artisan tmux:start' to create it"); + + return Command::FAILURE; + } + + $this->info("📎 Attaching to session '{$sessionName}'..."); + $this->info('💡 Press Ctrl+A then D to detach'); + + // Execute tmux attach + passthru("tmux attach -t {$sessionName}"); + + return Command::SUCCESS; + } +} diff --git a/app/Console/Commands/TmuxStart.php b/app/Console/Commands/TmuxStart.php new file mode 100644 index 000000000..ccde6feee --- /dev/null +++ b/app/Console/Commands/TmuxStart.php @@ -0,0 +1,194 @@ +colorCli = new ColorCLI; + + try { + $this->colorCli->header('Starting Tmux Processing'); + + // Get session name + $sessionName = $this->option('session') + ?? Settings::settingValue('tmux_session') + ?? config('tmux.session.default_name', 'nntmux'); + + // Initialize services + $this->sessionManager = new TmuxSessionManager($sessionName); + $this->layoutBuilder = new TmuxLayoutBuilder($this->sessionManager); + + // Check if tmux is installed + if (! $this->checkTmuxInstalled()) { + $this->error('❌ tmux is not installed'); + + return Command::FAILURE; + } + + // Check if session already exists + if ($this->sessionManager->sessionExists()) { + if (! $this->option('force')) { + $this->error("❌ Session '{$sessionName}' already exists"); + if ($this->confirm('Would you like to restart it?', false)) { + $this->call('tmux:stop', ['--session' => $sessionName]); + sleep(2); + } else { + return Command::FAILURE; + } + } else { + $this->call('tmux:stop', ['--session' => $sessionName]); + sleep(2); + } + } + + // Reset old collections + $this->info('🔄 Resetting old collections...'); + $this->resetOldCollections(); + + // Get sequential mode + $sequential = (int) (Settings::settingValue('sequential') ?? 0); + $this->info("📐 Building layout (mode: {$sequential})"); + + // Build the tmux layout + if (! $this->layoutBuilder->buildLayout($sequential)) { + $this->error('❌ Failed to build tmux layout'); + + return Command::FAILURE; + } + + $this->info('✅ Tmux layout created'); + + // Set running flag + Settings::query()->where('name', 'running')->update(['value' => 1]); + $this->info('✅ Running flag set'); + + // Start monitor in background + $this->info('🚀 Starting monitor...'); + $this->startMonitor($sessionName); + + $this->info("✅ Tmux session '{$sessionName}' started successfully"); + + // Attach if requested + if ($this->option('attach')) { + $this->info('📎 Attaching to session...'); + $this->sessionManager->attachSession(); + } else { + $this->info("💡 To attach to the session, run: tmux attach -t {$sessionName}"); + $this->info('💡 Or use: php artisan tmux:attach'); + } + + return Command::SUCCESS; + + } catch (\Exception $e) { + $this->error('❌ Failed to start tmux: '.$e->getMessage()); + logger()->error('Tmux start error', [ + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + return Command::FAILURE; + } + } + + /** + * Check if tmux is installed + */ + private function checkTmuxInstalled(): bool + { + $result = \Illuminate\Support\Facades\Process::timeout(5) + ->run('which tmux 2>/dev/null'); + + return $result->successful() && str_contains($result->output(), 'tmux'); + } + + /** + * Reset old collections + */ + private function resetOldCollections(): void + { + $delayTime = (int) (Settings::settingValue('delaytime') ?? 2); + + try { + DB::transaction(function () use ($delayTime) { + $count = Collection::query() + ->where('dateadded', '<', now()->subHours($delayTime)) + ->update(['dateadded' => now()]); + + if ($count > 0) { + $this->info(" ✓ Reset {$count} expired collections"); + } else { + $this->info(' ✓ No collections needed resetting'); + } + }, 10); + + } catch (\Exception $e) { + $this->warn(' ⚠ Failed to reset collections: '.$e->getMessage()); + } + } + + /** + * Start the monitor process + */ + private function startMonitor(string $sessionName): void + { + $paneManager = new \App\Services\Tmux\TmuxPaneManager($sessionName); + + // Priority: new monitor > old monitor > artisan command + $newMonitor = base_path('app/Services/Tmux/Scripts/monitor.php'); + $oldMonitor = base_path('misc/update/tmux/monitor.php'); + + if (file_exists($newMonitor)) { + // Use the new modernized monitor script + $command = "php {$newMonitor}"; + $this->info(' ✓ Using modernized monitor script'); + } elseif (file_exists($oldMonitor)) { + // Fall back to original monitor script + $command = "php {$oldMonitor}"; + $this->warn(' ⚠ Using legacy monitor.php (consider updating)'); + } else { + // Last resort: use artisan command (not ideal for pane) + $artisan = base_path('artisan'); + $command = "php {$artisan} tmux:monitor --session={$sessionName}"; + $this->warn(' ⚠ Using artisan command (not recommended for pane)'); + } + + // Spawn monitor in pane 0.0 + $paneManager->respawnPane('0.0', $command); + } +} diff --git a/app/Console/Commands/TmuxStop.php b/app/Console/Commands/TmuxStop.php new file mode 100644 index 000000000..5295c0721 --- /dev/null +++ b/app/Console/Commands/TmuxStop.php @@ -0,0 +1,91 @@ +colorCli = new ColorCLI; + + try { + // Get session name + $sessionName = $this->option('session') + ?? Settings::settingValue('tmux_session') + ?? config('tmux.session.default_name', 'nntmux'); + + $this->sessionManager = new TmuxSessionManager($sessionName); + + // Check if session exists + if (! $this->sessionManager->sessionExists()) { + $this->warn("⚠️ Session '{$sessionName}' is not running"); + + return Command::SUCCESS; + } + + // Confirm unless forced + if (! $this->option('force')) { + if (! $this->confirm("Stop tmux session '{$sessionName}'?", true)) { + $this->info('Cancelled'); + + return Command::SUCCESS; + } + } + + $this->colorCli->header('Stopping Tmux Session'); + + // Set running flag to 0 + Settings::query()->where('name', 'running')->update(['value' => 0]); + $this->info('✅ Running flag cleared'); + + // Wait for panes to shut down gracefully + $delay = (int) (Settings::settingValue('monitor_delay') ?? 10); + $this->info("⏳ Waiting {$delay} seconds for panes to shut down gracefully..."); + sleep($delay); + + // Kill the session + if ($this->sessionManager->killSession()) { + $this->info("✅ Session '{$sessionName}' stopped successfully"); + + return Command::SUCCESS; + } else { + $this->error("❌ Failed to stop session '{$sessionName}'"); + + return Command::FAILURE; + } + + } catch (\Exception $e) { + $this->error('❌ Failed to stop tmux: '.$e->getMessage()); + + return Command::FAILURE; + } + } +} diff --git a/app/Console/Commands/TmuxUIRestart.php b/app/Console/Commands/TmuxUIRestart.php index 1ca847a2d..5db243e17 100644 --- a/app/Console/Commands/TmuxUIRestart.php +++ b/app/Console/Commands/TmuxUIRestart.php @@ -18,24 +18,18 @@ class TmuxUIRestart extends Command * * @var string */ - protected $description = 'Restart processing of tmux scripts completely'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } + protected $description = 'Restart processing of tmux scripts (deprecated - use tmux:stop && tmux:start)'; /** * Execute the console command. */ - public function handle(): void + public function handle(): int { - $this->call('tmux-ui:stop', ['--kill' => true]); - $this->call('tmux-ui:start'); + $this->warn('⚠️ This command is deprecated. Use "php artisan tmux:stop && php artisan tmux:start" instead.'); + + $this->call('tmux:stop', ['--force' => true]); + sleep(2); + + return $this->call('tmux:start'); } } diff --git a/app/Console/Commands/TmuxUIStart.php b/app/Console/Commands/TmuxUIStart.php index 5560106d1..c822b514e 100644 --- a/app/Console/Commands/TmuxUIStart.php +++ b/app/Console/Commands/TmuxUIStart.php @@ -2,7 +2,6 @@ namespace App\Console\Commands; -use App\Models\Settings; use App\Support\UpdatePerformanceHelper; use Blacklight\Tmux; use Illuminate\Console\Command; @@ -34,36 +33,13 @@ class TmuxUIStart extends Command { try { $this->info('🚀 Starting Tmux UI...'); + $this->warn('⚠️ This command is deprecated. Use "php artisan tmux:start" instead.'); - $tmux = new Tmux; - $tmuxSession = Settings::settingValue('tmux_session') ?? 0; - $timeout = (int) $this->option('timeout'); - - // Check system resources before starting - $this->checkSystemResources(); - - // Check if session already exists - if (! $this->option('force') && $this->isSessionRunning($tmuxSession)) { - $this->error("❌ Tmux session '$tmuxSession' is already running"); - - if (! $this->confirm('Would you like to restart the session?')) { - return Command::FAILURE; - } - - $this->call('tmux-ui:stop', ['--kill' => true]); - sleep(2); // Brief pause to ensure clean shutdown - } - - // Set running state - $tmux->startRunning(); - $this->line(' ✓ Tmux running state activated'); - - // Start tmux session - $this->startTmuxSession($tmuxSession, $timeout); - - $this->info('✅ Tmux UI started successfully'); - - return Command::SUCCESS; + // Delegate to new command + return $this->call('tmux:start', [ + '--force' => $this->option('force'), + '--attach' => $this->option('monitor'), + ]); } catch (\Exception $e) { $this->error('❌ Failed to start Tmux UI: '.$e->getMessage()); diff --git a/app/Console/Commands/TmuxUIStop.php b/app/Console/Commands/TmuxUIStop.php index a65b4fc75..37ba752cf 100644 --- a/app/Console/Commands/TmuxUIStop.php +++ b/app/Console/Commands/TmuxUIStop.php @@ -2,10 +2,7 @@ namespace App\Console\Commands; -use App\Models\Settings; -use Blacklight\Tmux; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Process; class TmuxUIStop extends Command { @@ -21,24 +18,17 @@ class TmuxUIStop extends Command * * @var string */ - protected $description = 'Stop the processing of tmux scripts.'; + protected $description = 'Stop the processing of tmux scripts (deprecated - use tmux:stop)'; /** - * @throws \Exception + * Execute the console command */ - public function handle(): void + public function handle(): int { - $tmux = new Tmux; - $tmux->stopIfRunning(); - if ($this->option('kill') === true) { - $sessionName = Settings::settingValue('tmux_session'); - $tmuxSession = Process::run('tmux kill-session -t '.$sessionName); - $this->info('Killing active tmux session: '.$sessionName); - if ($tmuxSession->successful()) { - $this->info('Tmux session killed successfully'); - } else { - $this->info('No valid tmux sessions found'); - } - } + $this->warn('⚠️ This command is deprecated. Use "php artisan tmux:stop" instead.'); + + return $this->call('tmux:stop', [ + '--force' => $this->option('kill'), + ]); } } diff --git a/app/Services/Tmux/Scripts/monitor.php b/app/Services/Tmux/Scripts/monitor.php new file mode 100644 index 000000000..d34f5a34d --- /dev/null +++ b/app/Services/Tmux/Scripts/monitor.php @@ -0,0 +1,128 @@ +#!/usr/bin/env php +initializeMonitor(); + + // Add paths that TmuxRun expects + $runVar['paths']['misc'] = base_path().'/misc/'; + $runVar['paths']['cli'] = base_path().'/cli/'; + + // Also initialize old-style commands for compatibility + $tmux_niceness = Settings::settingValue('niceness') ?? 2; + $runVar['commands']['_php'] = " nice -n{$tmux_niceness} php"; + $runVar['commands']['_phpn'] = "nice -n{$tmux_niceness} php"; + $runVar['commands']['_sleep'] = "{$runVar['commands']['_phpn']} ".base_path('app/Services/Tmux/Scripts/showsleep.php'); + + // Add scripts paths for TmuxRun - Using multiprocessing scripts WITH ARGUMENTS + $runVar['scripts']['binaries'] = "nice -n{$tmux_niceness} php ".base_path('misc/update/multiprocessing/binaries.php').' 0'; + $runVar['scripts']['backfill'] = "nice -n{$tmux_niceness} php ".base_path('misc/update/multiprocessing/backfill.php').' 0'; + $runVar['scripts']['releases'] = "nice -n{$tmux_niceness} php ".base_path('misc/update/multiprocessing/releases.php'); + + // Spawn IRC scraper immediately + try { + $taskRunner->runIRCScraper(['constants' => $runVar['constants']]); + } catch (Exception $e) { + logger()->error('Failed to spawn IRC scraper: '.$e->getMessage()); + } + + // Get list of panes for compatibility + $runVar['panes'] = $tRun->getListOfPanes($runVar['constants']); + + $colorCli->header('Tmux Monitor Started'); + echo "Session: {$sessionName}\n"; + echo "Press Ctrl+C to stop (or set exit flag in settings)\n\n"; + + // Main monitoring loop + while ($monitor->shouldContinue()) { + // Collect statistics (but preserve our custom settings) + $scriptsBackup = $runVar['scripts'] ?? []; + $commandsBackup = $runVar['commands'] ?? []; + $pathsBackup = $runVar['paths'] ?? []; + + $runVar = $monitor->collectStatistics(); + + // Restore our custom settings that collectStatistics doesn't know about + $runVar['scripts'] = $scriptsBackup; + $runVar['commands'] = $commandsBackup; + $runVar['paths'] = array_merge($runVar['paths'] ?? [], $pathsBackup); + + // Refresh panes list periodically + $runVar['panes'] = $tRun->getListOfPanes($runVar['constants']); + + // Update display + $output->updateMonitorPane($runVar); + + // Run pane tasks if tmux is running + if ((int) ($runVar['settings']['is_running'] ?? 0) === 1) { + $sequential = (int) ($runVar['constants']['sequential'] ?? 0); + + // Determine which panes to run based on sequential mode + $panesToRun = match ($sequential) { + 0 => ['main', 'fixnames', 'removecrap', 'ppadditional', 'nonamazon', 'amazon'], + 1 => ['main', 'fixnames', 'removecrap', 'ppadditional', 'nonamazon', 'amazon'], + 2 => ['main', 'fixnames', 'amazon'], + default => [], + }; + + // Run each pane task + foreach ($panesToRun as $paneName) { + try { + // Use old TmuxRun for compatibility during transition + $tRun->runPane($paneName, $runVar); + } catch (Exception $e) { + logger()->error("Failed to run pane {$paneName}: ".$e->getMessage()); + } + } + } + + // Increment iteration and sleep + $monitor->incrementIteration(); + sleep(10); + } + + $colorCli->info('Monitor stopped by exit flag'); + +} catch (Exception $e) { + $colorCli->error('Monitor failed: '.$e->getMessage()); + logger()->error('Tmux monitor error', [ + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + exit(1); +} diff --git a/app/Services/Tmux/Scripts/showsleep.php b/app/Services/Tmux/Scripts/showsleep.php new file mode 100644 index 000000000..30bcbfe05 --- /dev/null +++ b/app/Services/Tmux/Scripts/showsleep.php @@ -0,0 +1,25 @@ +sessionManager = $sessionManager; + $this->sessionName = $sessionManager->getSessionName(); + $this->paneManager = new TmuxPaneManager($this->sessionName); + } + + /** + * Build the appropriate layout based on sequential mode + */ + public function buildLayout(int $sequentialMode): bool + { + return match ($sequentialMode) { + 1 => $this->buildBasicLayout(), + 2 => $this->buildStrippedLayout(), + default => $this->buildFullLayout(), + }; + } + + /** + * Build full non-sequential layout (mode 0) + */ + protected function buildFullLayout(): bool + { + // Window 0: Monitor + Binaries + Backfill + Releases + if (! $this->sessionManager->createSession('Monitor')) { + return false; + } + + // Select pane 0.0, then split for binaries (right side, 67%) + $this->paneManager->selectPane('0.0'); + $this->paneManager->splitHorizontal('0', '67%', 'update_binaries'); + + // Select pane 0.1 (binaries), then split for backfill (bottom, 67%) + $this->paneManager->selectPane('0.1'); + $this->paneManager->splitVertical('0', '67%', 'backfill'); + + // Split again for releases (bottom, 50%) + $this->paneManager->splitVertical('0', '50%', 'update_releases'); + + // Window 1: Fix names + Remove crap + $this->paneManager->createWindow(1, 'utils'); + $this->paneManager->setPaneTitle('1.0', 'fixReleaseNames'); + $this->paneManager->selectPane('1.0'); + $this->paneManager->splitHorizontal('1', '50%', 'removeCrapReleases'); + + // Window 2: Postprocessing (Additional + Non-Amazon + Amazon) + $this->paneManager->createWindow(2, 'post'); + $this->paneManager->setPaneTitle('2.0', 'postprocessing_additional'); + $this->paneManager->splitVertical('2', '67%', 'postprocessing_non_amazon'); + $this->paneManager->splitVertical('2', '50%', 'postprocessing_amazon'); + + // Window 3: IRC Scraper + $this->createIRCScraperWindow(); + + // Additional windows (optional monitoring tools) + $this->createOptionalWindows(); + + return true; + } + + /** + * Build basic sequential layout (mode 1) + */ + protected function buildBasicLayout(): bool + { + // Window 0: Monitor + Releases + if (! $this->sessionManager->createSession('Monitor')) { + return false; + } + + // Select pane 0.0, then split for releases + $this->paneManager->selectPane('0.0'); + $this->paneManager->splitHorizontal('0', '67%', 'update_releases'); + + // Window 1: Utils (Fix names + Remove crap) + $this->paneManager->createWindow(1, 'utils'); + $this->paneManager->setPaneTitle('1.0', 'fixReleaseNames'); + $this->paneManager->selectPane('1.0'); + $this->paneManager->splitHorizontal('1', '50%', 'removeCrapReleases'); + + // Window 2: Postprocessing + $this->paneManager->createWindow(2, 'post'); + $this->paneManager->setPaneTitle('2.0', 'postprocessing_additional'); + $this->paneManager->splitVertical('2', '67%', 'postprocessing_non_amazon'); + $this->paneManager->splitVertical('2', '50%', 'postprocessing_amazon'); + + // Window 3: IRC Scraper + $this->createIRCScraperWindow(); + + $this->createOptionalWindows(); + + return true; + } + + /** + * Build stripped sequential layout (mode 2) + */ + protected function buildStrippedLayout(): bool + { + // Window 0: Monitor + Sequential + if (! $this->sessionManager->createSession('Monitor')) { + return false; + } + + // Select pane 0.0, then split for sequential + $this->paneManager->selectPane('0.0'); + $this->paneManager->splitHorizontal('0', '67%', 'sequential'); + + // Window 1: Amazon postprocessing + $this->paneManager->createWindow(1, 'utils'); + $this->paneManager->setPaneTitle('1.0', 'fixReleaseNames'); + $this->paneManager->selectPane('1.0'); + $this->paneManager->splitHorizontal('1', '50%', 'postprocessing_amazon'); + + // Window 2: IRC Scraper + $this->createIRCScraperWindow(); + + $this->createOptionalWindows(); + + return true; + } + + /** + * Create IRC scraper window + */ + protected function createIRCScraperWindow(): bool + { + $this->paneManager->createWindow(3, 'IRCScraper'); + $this->paneManager->setPaneTitle('3.0', 'scrapeIRC'); + + return true; + } + + /** + * Create optional monitoring windows based on settings + */ + protected function createOptionalWindows(): void + { + $windowIndex = 4; + + // htop + if ((int) Settings::settingValue('htop') === 1 && $this->commandExists('htop')) { + $this->paneManager->createWindow($windowIndex, 'htop'); + $this->paneManager->respawnPane("{$windowIndex}.0", 'htop'); + $windowIndex++; + } + + // nmon + if ((int) Settings::settingValue('nmon') === 1 && $this->commandExists('nmon')) { + $this->paneManager->createWindow($windowIndex, 'nmon'); + $this->paneManager->respawnPane("{$windowIndex}.0", 'nmon -t'); + $windowIndex++; + } + + // vnstat + if ((int) Settings::settingValue('vnstat') === 1 && $this->commandExists('vnstat')) { + $vnstatArgs = Settings::settingValue('vnstat_args') ?? ''; + $this->paneManager->createWindow($windowIndex, 'vnstat'); + $this->paneManager->respawnPane("{$windowIndex}.0", "watch -n10 'vnstat {$vnstatArgs}'"); + $windowIndex++; + } + + // tcptrack + if ((int) Settings::settingValue('tcptrack') === 1 && $this->commandExists('tcptrack')) { + $tcptrackArgs = Settings::settingValue('tcptrack_args') ?? ''; + $this->paneManager->createWindow($windowIndex, 'tcptrack'); + $this->paneManager->respawnPane("{$windowIndex}.0", "tcptrack {$tcptrackArgs}"); + $windowIndex++; + } + + // bwm-ng + if ((int) Settings::settingValue('bwmng') === 1 && $this->commandExists('bwm-ng')) { + $this->paneManager->createWindow($windowIndex, 'bwm-ng'); + $this->paneManager->respawnPane("{$windowIndex}.0", 'bwm-ng'); + $windowIndex++; + } + + // mytop + if ((int) Settings::settingValue('mytop') === 1 && $this->commandExists('mytop')) { + $this->paneManager->createWindow($windowIndex, 'mytop'); + $this->paneManager->respawnPane("{$windowIndex}.0", 'mytop -u'); + $windowIndex++; + } + + // redis-stat + if ((int) Settings::settingValue('redis') === 1 && $this->commandExists('redis-cli')) { + $this->paneManager->createWindow($windowIndex, 'redis-stat'); + $this->paneManager->respawnPane("{$windowIndex}.0", 'redis-stat --verbose --server=63790'); + $windowIndex++; + } + + // bash console + if ((int) Settings::settingValue('console') === 1) { + $this->paneManager->createWindow($windowIndex, 'bash'); + $this->paneManager->respawnPane("{$windowIndex}.0", 'bash -i'); + } + } + + /** + * Check if a command exists + */ + protected function commandExists(string $command): bool + { + $result = \Illuminate\Support\Facades\Process::timeout(5) + ->run("which {$command} 2>/dev/null"); + + return $result->successful() && str_contains($result->output(), $command); + } +} diff --git a/app/Services/Tmux/TmuxMonitorService.php b/app/Services/Tmux/TmuxMonitorService.php new file mode 100644 index 000000000..d6b5ed742 --- /dev/null +++ b/app/Services/Tmux/TmuxMonitorService.php @@ -0,0 +1,419 @@ +tmux = new Tmux; + } + + /** + * Initialize monitor with default values + */ + public function initializeMonitor(): array + { + $this->runVar['paths']['misc'] = base_path().'/misc/'; + $this->runVar['paths']['cli'] = base_path().'/cli/'; + $this->runVar['paths']['scraper'] = base_path().'/misc/IRCScraper/scrape.php'; + + $this->runVar['constants'] = $this->tmux->getConstantSettings(); + $this->runVar['settings'] = $this->tmux->getMonitorSettings(); + $this->runVar['connections'] = $this->tmux->getConnectionsInfo($this->runVar['constants']); + + // Initialize timers + $this->runVar['timers'] = $this->initializeTimers(); + + // Initialize counts + $this->runVar['counts'] = [ + 'iterations' => 1, + 'now' => [], + 'start' => [], + 'diff' => [], + 'percent' => [], + ]; + + return $this->runVar; + } + + /** + * Initialize all timers + */ + protected function initializeTimers(): array + { + $now = time(); + + return [ + 'timer1' => $now, + 'timer2' => $now, + 'timer3' => $now, + 'timer4' => $now, + 'timer5' => $now, + 'query' => [ + 'tmux_time' => 0, + 'split_time' => 0, + 'init_time' => 0, + 'proc1_time' => 0, + 'proc2_time' => 0, + 'proc3_time' => 0, + 'split1_time' => 0, + 'init1_time' => 0, + 'proc11_time' => 0, + 'proc21_time' => 0, + 'proc31_time' => 0, + 'tpg_time' => 0, + 'tpg1_time' => 0, + ], + 'newOld' => [ + 'newestrelname' => '', + 'oldestcollection' => 0, + 'newestpre' => 0, + 'newestrelease' => 0, + ], + ]; + } + + /** + * Collect current statistics + */ + public function collectStatistics(): array + { + // Refresh settings periodically + $monitorDelay = (int) ($this->runVar['settings']['monitor'] ?? 60); + $timeSinceLastRefresh = time() - ($this->runVar['timers']['timer2'] ?? 0); + + if ($this->iterations === 1 || $timeSinceLastRefresh >= $monitorDelay) { + $this->refreshStatistics(); + $this->runVar['timers']['timer2'] = time(); + } + + // Update connection counts + $this->updateConnectionCounts(); + + // Set killswitches + $this->setKillswitches(); + + return $this->runVar; + } + + /** + * Refresh all statistics from database + */ + protected function refreshStatistics(): void + { + $timer = time(); + + // Refresh settings + $this->runVar['settings'] = $this->tmux->getMonitorSettings(); + $this->runVar['timers']['query']['tmux_time'] = time() - $timer; + + // Get category counts + $this->getCategoryCounts(); + + // Get process counts + $this->getProcessCounts(); + + // Get table counts + $this->getTableCounts(); + + // Calculate diffs and percentages + $this->calculateStatistics(); + } + + /** + * Get counts by category + */ + protected function getCategoryCounts(): void + { + $timer = time(); + + $this->runVar['counts']['now']['tv'] = Release::query() + ->whereBetween('categories_id', [Category::TV_ROOT, Category::TV_OTHER]) + ->count('id'); + + $this->runVar['counts']['now']['movies'] = Release::query() + ->whereBetween('categories_id', [Category::MOVIE_ROOT, Category::MOVIE_OTHER]) + ->count('id'); + + $this->runVar['counts']['now']['audio'] = Release::query() + ->whereBetween('categories_id', [Category::MUSIC_ROOT, Category::MUSIC_OTHER]) + ->count('id'); + + $this->runVar['counts']['now']['books'] = Release::query() + ->whereBetween('categories_id', [Category::BOOKS_ROOT, Category::BOOKS_UNKNOWN]) + ->count('id'); + + $this->runVar['counts']['now']['console'] = Release::query() + ->whereBetween('categories_id', [Category::GAME_ROOT, Category::GAME_OTHER]) + ->count('id'); + + $this->runVar['counts']['now']['pc'] = Release::query() + ->whereBetween('categories_id', [Category::PC_ROOT, Category::PC_PHONE_ANDROID]) + ->count('id'); + + $this->runVar['counts']['now']['xxx'] = Release::query() + ->whereBetween('categories_id', [Category::XXX_ROOT, Category::XXX_OTHER]) + ->count('id'); + + $this->runVar['counts']['now']['misc'] = Release::query() + ->whereBetween('categories_id', [Category::OTHER_ROOT, Category::OTHER_HASHED]) + ->count('id'); + + $this->runVar['timers']['query']['init_time'] = time() - $timer; + } + + /** + * Get process-related counts + */ + protected function getProcessCounts(): void + { + $timer = time(); + + try { + $bookReqIds = $this->runVar['settings']['book_reqids'] ?? Category::BOOKS_ROOT; + $dbName = config('nntmux.db_name'); + + $proc1Query = $this->tmux->proc_query(1, $bookReqIds, $dbName); + $proc1Result = DB::selectOne($proc1Query); + + if ($proc1Result) { + foreach ((array) $proc1Result as $key => $value) { + $this->runVar['counts']['now'][$key] = $value; + } + } + + $this->runVar['timers']['query']['proc1_time'] = time() - $timer; + + // Process 2 + $timer2 = time(); + $maxSize = $this->runVar['settings']['maxsize_pp'] ?? ''; + $minSize = $this->runVar['settings']['minsize_pp'] ?? ''; + + $proc2Query = $this->tmux->proc_query(2, $bookReqIds, $dbName, $maxSize, $minSize); + $proc2Result = DB::selectOne($proc2Query); + + if ($proc2Result) { + foreach ((array) $proc2Result as $key => $value) { + $this->runVar['counts']['now'][$key] = $value; + } + } + + $this->runVar['timers']['query']['proc2_time'] = time() - $timer2; + + } catch (\Exception $e) { + logger()->error('Error collecting process counts: '.$e->getMessage()); + } + } + + /** + * Get table row counts + */ + protected function getTableCounts(): void + { + $timer = time(); + + try { + $this->runVar['counts']['now']['collections_table'] = Collection::query()->count(); + + // Get binaries/parts counts from information_schema or use approximation + $dbName = config('nntmux.db_name'); + $tables = $this->tmux->cbpmTableQuery(); + + $this->runVar['counts']['now']['binaries_table'] = 0; + $this->runVar['counts']['now']['parts_table'] = 0; + $this->runVar['counts']['now']['missed_parts_table'] = 0; + + foreach ($tables as $table) { + $tableName = $table->name; + $count = $this->getTableRowCount($tableName); + + if (str_contains($tableName, 'binaries')) { + $this->runVar['counts']['now']['binaries_table'] += $count; + } elseif (str_contains($tableName, 'missed_parts')) { + $this->runVar['counts']['now']['missed_parts_table'] += $count; + } elseif (str_contains($tableName, 'parts')) { + $this->runVar['counts']['now']['parts_table'] += $count; + } + } + + $this->runVar['timers']['query']['tpg_time'] = time() - $timer; + + // Get additional table counts (query 4) + $timer4 = time(); + $bookReqIds = $this->runVar['settings']['book_reqids'] ?? Category::BOOKS_ROOT; + $proc4Query = $this->tmux->proc_query(4, $bookReqIds, $dbName); + $proc4Result = DB::selectOne($proc4Query); + + if ($proc4Result) { + foreach ((array) $proc4Result as $key => $value) { + $this->runVar['counts']['now'][$key] = $value; + } + } + + // Get newest/oldest data (query 6) + $timer6 = time(); + $proc6Query = $this->tmux->proc_query(6, $bookReqIds, $dbName); + $proc6Result = DB::selectOne($proc6Query); + + if ($proc6Result) { + foreach ((array) $proc6Result as $key => $value) { + $this->runVar['timers']['newOld'][$key] = $value; + } + } + + } catch (\Exception $e) { + logger()->error('Error collecting table counts: '.$e->getMessage()); + } + } + + /** + * Get row count for a table + */ + protected function getTableRowCount(string $tableName): int + { + try { + $result = DB::selectOne( + 'SELECT TABLE_ROWS AS count FROM information_schema.TABLES + WHERE TABLE_NAME = ? AND TABLE_SCHEMA = DATABASE()', + [$tableName] + ); + + return (int) ($result->count ?? 0); + } catch (\Exception $e) { + return 0; + } + } + + /** + * Calculate statistics (diffs, percentages, totals) + */ + protected function calculateStatistics(): void + { + // Calculate total work + $this->runVar['counts']['now']['total_work'] = 0; + + foreach ($this->runVar['counts']['now'] as $key => $value) { + if (str_starts_with($key, 'process')) { + $this->runVar['counts']['now']['total_work'] += $value; + } + } + + // Set initial start values on first iteration + if ($this->iterations === 1) { + $this->runVar['counts']['start'] = $this->runVar['counts']['now']; + } + + // Calculate diffs and percentages + $totalReleases = $this->runVar['counts']['now']['releases'] ?? 1; + + foreach ($this->runVar['counts']['now'] as $key => $value) { + $startValue = $this->runVar['counts']['start'][$key] ?? 0; + $this->runVar['counts']['diff'][$key] = number_format($value - $startValue); + $this->runVar['counts']['percent'][$key] = $totalReleases > 0 + ? sprintf('%02d', floor(($value / $totalReleases) * 100)) + : 0; + } + } + + /** + * Update connection counts + */ + protected function updateConnectionCounts(): void + { + $this->runVar['conncounts'] = $this->tmux->getUSPConnections( + 'primary', + $this->runVar['connections'] + ); + + if ((int) ($this->runVar['constants']['alternate_nntp'] ?? 0) === 1) { + $alternateConns = $this->tmux->getUSPConnections( + 'alternate', + $this->runVar['connections'] + ); + $this->runVar['conncounts'] = array_merge($this->runVar['conncounts'], $alternateConns); + } + } + + /** + * Set killswitches based on limits + */ + protected function setKillswitches(): void + { + $ppKillLimit = (int) ($this->runVar['settings']['postprocess_kill'] ?? 0); + $collKillLimit = (int) ($this->runVar['settings']['collections_kill'] ?? 0); + + $totalWork = (int) ($this->runVar['counts']['now']['total_work'] ?? 0); + $collections = (int) ($this->runVar['counts']['now']['collections_table'] ?? 0); + + $this->runVar['killswitch']['pp'] = ($ppKillLimit > 0 && $ppKillLimit < $totalWork); + $this->runVar['killswitch']['coll'] = ($collKillLimit > 0 && $collKillLimit < $collections); + } + + /** + * Update the monitor display + */ + public function updateDisplay(): void + { + // This would output to terminal - implementation depends on TmuxOutput + // For now, we'll just log basic info + if ($this->iterations % 10 === 0) { + logger()->info('Tmux Monitor', [ + 'iteration' => $this->iterations, + 'total_work' => $this->runVar['counts']['now']['total_work'] ?? 0, + 'collections' => $this->runVar['counts']['now']['collections_table'] ?? 0, + ]); + } + } + + /** + * Increment iteration counter + */ + public function incrementIteration(): void + { + $this->iterations++; + $this->runVar['counts']['iterations'] = $this->iterations; + } + + /** + * Check if monitoring should continue + */ + public function shouldContinue(): bool + { + $exitFlag = (int) Settings::settingValue('exit'); + + if ($exitFlag === 0) { + return true; + } + + $this->shouldContinue = false; + + return false; + } + + /** + * Get current run variables + */ + public function getRunVar(): array + { + return $this->runVar; + } +} diff --git a/app/Services/Tmux/TmuxPaneManager.php b/app/Services/Tmux/TmuxPaneManager.php new file mode 100644 index 000000000..09922c23c --- /dev/null +++ b/app/Services/Tmux/TmuxPaneManager.php @@ -0,0 +1,146 @@ +sessionName = $sessionName; + } + + /** + * Create a new window + */ + public function createWindow(int $index, string $name): bool + { + $result = Process::timeout(10)->run( + "tmux new-window -t {$this->sessionName}:{$index} -n {$name} 'printf \"\\033]2;{$name}\\033\"'" + ); + + return $result->successful(); + } + + /** + * Split a pane horizontally + */ + public function splitHorizontal(string $target, string $percentage, string $title = ''): bool + { + $titleCmd = $title ? "printf \"\\033]2;{$title}\\033\"" : ''; + + $result = Process::timeout(10)->run( + "tmux splitw -t {$this->sessionName}:{$target} -h -l {$percentage} '{$titleCmd}'" + ); + + return $result->successful(); + } + + /** + * Split a pane vertically + */ + public function splitVertical(string $target, string $percentage, string $title = ''): bool + { + $titleCmd = $title ? "printf \"\\033]2;{$title}\\033\"" : ''; + + $result = Process::timeout(10)->run( + "tmux splitw -t {$this->sessionName}:{$target} -v -l {$percentage} '{$titleCmd}'" + ); + + return $result->successful(); + } + + /** + * Select a specific pane + */ + public function selectPane(string $target): bool + { + $result = Process::timeout(5)->run( + "tmux selectp -t {$this->sessionName}:{$target}" + ); + + return $result->successful(); + } + + /** + * Respawn a pane with a new command + */ + public function respawnPane(string $target, string $command, bool $kill = false): bool + { + $killFlag = $kill ? '-k' : ''; + + $result = Process::timeout(10)->run( + "tmux respawnp {$killFlag} -t {$this->sessionName}:{$target} '{$command}'" + ); + + return $result->successful(); + } + + /** + * Send keys to a pane + */ + public function sendKeys(string $target, string $keys, bool $enter = true): bool + { + $enterKey = $enter ? ' Enter' : ''; + + $result = Process::timeout(5)->run( + "tmux send-keys -t {$this->sessionName}:{$target} '{$keys}'{$enterKey}" + ); + + return $result->successful(); + } + + /** + * Kill a specific pane + */ + public function killPane(string $target): bool + { + $result = Process::timeout(5)->run( + "tmux kill-pane -t {$this->sessionName}:{$target}" + ); + + return $result->successful(); + } + + /** + * Set pane title + */ + public function setPaneTitle(string $target, string $title): bool + { + $result = Process::timeout(5)->run( + "tmux select-pane -t {$this->sessionName}:{$target} -T '{$title}'" + ); + + return $result->successful(); + } + + /** + * Get pane title + */ + public function getPaneTitle(string $target): ?string + { + $result = Process::timeout(5)->run( + "tmux display-message -p -t {$this->sessionName}:{$target} '#{pane_title}'" + ); + + return $result->successful() ? trim($result->output()) : null; + } + + /** + * Capture pane content + */ + public function capturePane(string $target, int $lines = 100): string + { + $result = Process::timeout(10)->run( + "tmux capture-pane -p -t {$this->sessionName}:{$target} -S -{$lines}" + ); + + return $result->successful() ? $result->output() : ''; + } +} diff --git a/app/Services/Tmux/TmuxSessionManager.php b/app/Services/Tmux/TmuxSessionManager.php new file mode 100644 index 000000000..8f101b576 --- /dev/null +++ b/app/Services/Tmux/TmuxSessionManager.php @@ -0,0 +1,145 @@ +sessionName = $sessionName ?? $this->getSessionName(); + $this->configFile = config('tmux.config_file'); + } + + /** + * Get the tmux session name from settings or config + */ + public function getSessionName(): string + { + return Settings::settingValue('tmux_session') + ?? config('tmux.session.name') + ?? config('tmux.session.default_name', 'nntmux'); + } + + /** + * Check if tmux session exists + */ + public function sessionExists(): bool + { + $result = Process::timeout(10) + ->run("tmux list-sessions 2>/dev/null | grep -q '^{$this->sessionName}:'"); + + return $result->successful(); + } + + /** + * Create a new tmux session + */ + public function createSession(string $windowName = 'Monitor'): bool + { + if ($this->sessionExists()) { + return false; + } + + $configOption = file_exists($this->configFile) ? "-f {$this->configFile}" : ''; + + $result = Process::timeout(30)->run( + "tmux {$configOption} new-session -d -s {$this->sessionName} -n {$windowName} 'printf \"\\033]2;{$windowName}\\033\"'" + ); + + return $result->successful(); + } + + /** + * Kill the tmux session + */ + public function killSession(): bool + { + if (! $this->sessionExists()) { + return true; + } + + $result = Process::timeout(30)->run("tmux kill-session -t {$this->sessionName}"); + + return $result->successful(); + } + + /** + * Attach to the tmux session + */ + public function attachSession(): bool + { + if (! $this->sessionExists()) { + return false; + } + + $result = Process::timeout(5)->run( + "tmux select-window -t {$this->sessionName}:0; tmux attach-session -d -t {$this->sessionName}" + ); + + return $result->successful(); + } + + /** + * List all panes in the session + */ + public function listPanes(): array + { + if (! $this->sessionExists()) { + return []; + } + + $result = Process::timeout(10)->run( + "tmux list-panes -s -t {$this->sessionName} -F '#{window_index}:#{pane_index} #{pane_title}'" + ); + + if (! $result->successful()) { + return []; + } + + $panes = []; + $lines = explode("\n", trim($result->output())); + + foreach ($lines as $line) { + if (empty($line)) { + continue; + } + + [$position, $title] = explode(' ', $line, 2); + $panes[$position] = $title; + } + + return $panes; + } + + /** + * Get pane status + */ + public function getPaneStatus(string $window, string $pane): ?string + { + $result = Process::timeout(5)->run( + "tmux display-message -p -t {$this->sessionName}:{$window}.{$pane} '#{pane_current_command}'" + ); + + return $result->successful() ? trim($result->output()) : null; + } + + /** + * Check if a pane is running a command + */ + public function isPaneActive(string $window, string $pane): bool + { + $status = $this->getPaneStatus($window, $pane); + + return $status !== null && $status !== 'bash' && $status !== 'sh'; + } +} diff --git a/app/Services/Tmux/TmuxTaskRunner.php b/app/Services/Tmux/TmuxTaskRunner.php new file mode 100644 index 000000000..21a019653 --- /dev/null +++ b/app/Services/Tmux/TmuxTaskRunner.php @@ -0,0 +1,521 @@ +sessionName = $sessionName; + $this->paneManager = new TmuxPaneManager($sessionName); + $this->colorCli = new ColorCLI; + } + + /** + * Run a task in a specific pane + */ + public function runTask(string $taskName, array $config): bool + { + $pane = $config['pane'] ?? null; + $command = $config['command'] ?? null; + $enabled = $config['enabled'] ?? true; + $workAvailable = $config['work_available'] ?? true; + + if (! $pane || ! $command) { + return false; + } + + // Check if task is enabled and has work + if (! $enabled) { + return $this->disablePane($pane, $taskName, 'disabled in settings'); + } + + if (! $workAvailable) { + return $this->disablePane($pane, $taskName, 'no work available'); + } + + // Respawn the pane with the command + return $this->paneManager->respawnPane($pane, $command); + } + + /** + * Disable a pane with a message + */ + protected function disablePane(string $pane, string $taskName, string $reason): bool + { + $color = $this->getRandomColor(); + $message = "echo \"\\033[38;5;{$color}m\\n{$taskName} has been disabled: {$reason}\""; + + return $this->paneManager->respawnPane($pane, $message, kill: true); + } + + /** + * Build a command with logging + */ + public function buildCommand(string $baseCommand, array $options = []): string + { + $parts = [$baseCommand]; + + // Add sleep timer at the end if specified + if (isset($options['sleep'])) { + $sleepCommand = $this->buildSleepCommand($options['sleep']); + $parts[] = "date +'%Y-%m-%d %T'"; + $parts[] = $sleepCommand; + } + + // Add logging if enabled + if (isset($options['log_pane'])) { + $logFile = $this->getLogFile($options['log_pane']); + $command = implode('; ', $parts); + + return "{$command} 2>&1 | tee -a {$logFile}"; + } + + return implode('; ', $parts); + } + + /** + * Build sleep command + */ + protected function buildSleepCommand(int $seconds): string + { + $niceness = Settings::settingValue('niceness') ?? 2; + $sleepScript = base_path('app/Services/Tmux/Scripts/showsleep.php'); + + if (file_exists($sleepScript)) { + return "nice -n{$niceness} php {$sleepScript} {$seconds}"; + } + + return "sleep {$seconds}"; + } + + /** + * Get log file path for a pane + */ + protected function getLogFile(string $paneName): string + { + $logsEnabled = (int) Settings::settingValue('write_logs') === 1; + + if (! $logsEnabled) { + return '/dev/null'; + } + + $logDir = config('tmux.paths.logs', storage_path('logs/tmux')); + + if (! is_dir($logDir)) { + mkdir($logDir, 0755, true); + } + + $date = now()->format('Y_m_d'); + + return "{$logDir}/{$paneName}-{$date}.log"; + } + + /** + * Get a random color for terminal output + */ + protected function getRandomColor(): int + { + $start = (int) Settings::settingValue('colors_start') ?? 0; + $end = (int) Settings::settingValue('colors_end') ?? 255; + $exclude = Settings::settingValue('colors_exc') ?? ''; + + if (empty($exclude)) { + return random_int($start, $end); + } + + $exceptions = array_map('intval', explode(',', $exclude)); + sort($exceptions); + + $number = random_int($start, $end - count($exceptions)); + + foreach ($exceptions as $exception) { + if ($number >= $exception) { + $number++; + } else { + break; + } + } + + return $number; + } + + /** + * Run the IRC scraper + */ + public function runIRCScraper(array $config): bool + { + $runScraper = (int) ($config['constants']['run_ircscraper'] ?? 0); + $pane = '3.0'; + + if ($runScraper !== 1) { + return $this->disablePane($pane, 'IRC Scraper', 'disabled in settings'); + } + + $scraperScript = base_path('misc/IRCScraper/scrape.php'); + + if (! file_exists($scraperScript)) { + return $this->disablePane($pane, 'IRC Scraper', 'script not found'); + } + + $niceness = Settings::settingValue('niceness') ?? 2; + $command = "nice -n{$niceness} php {$scraperScript}"; + $command = $this->buildCommand($command, ['log_pane' => 'scraper']); + + return $this->paneManager->respawnPane($pane, $command); + } + + /** + * Run binaries update + */ + public function runBinariesUpdate(array $config): bool + { + $enabled = (int) ($config['settings']['binaries_run'] ?? 0); + $killswitch = $config['killswitch']['pp'] ?? false; + $pane = '0.1'; + + if (! $enabled) { + return $this->disablePane($pane, 'Update Binaries', 'disabled in settings'); + } + + if ($killswitch) { + return $this->disablePane($pane, 'Update Binaries', 'postprocess kill limit exceeded'); + } + + $script = match ((int) $enabled) { + 1 => base_path('misc/update/multiprocessing/safe.php binaries'), + default => null, + }; + + if (! $script) { + return false; + } + + $niceness = Settings::settingValue('niceness') ?? 2; + $command = "nice -n{$niceness} php {$script}"; + $sleep = (int) ($config['settings']['bins_timer'] ?? 60); + $command = $this->buildCommand($command, ['log_pane' => 'binaries', 'sleep' => $sleep]); + + return $this->paneManager->respawnPane($pane, $command); + } + + /** + * Run backfill + */ + public function runBackfill(array $config): bool + { + $enabled = (int) ($config['settings']['backfill'] ?? 0); + $collKillswitch = $config['killswitch']['coll'] ?? false; + $ppKillswitch = $config['killswitch']['pp'] ?? false; + $pane = '0.2'; + + if (! $enabled) { + return $this->disablePane($pane, 'Backfill', 'disabled in settings'); + } + + if ($collKillswitch || $ppKillswitch) { + return $this->disablePane($pane, 'Backfill', 'kill limit exceeded'); + } + + $script = match ((int) $enabled) { + 1 => base_path('misc/update/multiprocessing/backfill.php'), + 4 => base_path('misc/update/multiprocessing/safe.php backfill'), + default => null, + }; + + if (! $script) { + return false; + } + + // Calculate sleep time (progressive if enabled) + $baseSleep = (int) ($config['settings']['back_timer'] ?? 600); + $collections = (int) ($config['counts']['now']['collections_table'] ?? 0); + $progressive = (int) ($config['settings']['progressive'] ?? 0); + + $sleep = ($progressive === 1 && floor($collections / 500) > $baseSleep) + ? floor($collections / 500) + : $baseSleep; + + $niceness = Settings::settingValue('niceness') ?? 2; + $command = "nice -n{$niceness} php {$script}"; + $command = $this->buildCommand($command, ['log_pane' => 'backfill', 'sleep' => $sleep]); + + return $this->paneManager->respawnPane($pane, $command); + } + + /** + * Run releases update + */ + public function runReleasesUpdate(array $config): bool + { + $enabled = (int) ($config['settings']['releases_run'] ?? 0); + $pane = $config['pane'] ?? '0.3'; + + if (! $enabled) { + return $this->disablePane($pane, 'Update Releases', 'disabled in settings'); + } + + $script = base_path('misc/update/multiprocessing/releases.php'); + $niceness = Settings::settingValue('niceness') ?? 2; + $command = "nice -n{$niceness} php {$script}"; + $sleep = (int) ($config['settings']['rel_timer'] ?? 60); + $command = $this->buildCommand($command, ['log_pane' => 'releases', 'sleep' => $sleep]); + + return $this->paneManager->respawnPane($pane, $command); + } + + /** + * Run a specific pane task based on task name + * + * @param string $taskName The name of the task to run + * @param array $config Configuration for the task (target pane, etc.) + * @param array $runVar Runtime variables and settings + * @return bool Success status + */ + public function runPaneTask(string $taskName, array $config, array $runVar): bool + { + $sequential = (int) ($runVar['constants']['sequential'] ?? 0); + + return match ($taskName) { + 'main' => $this->runMainTask($sequential, $runVar), + 'fixnames' => $this->runFixNamesTask($runVar), + 'removecrap' => $this->runRemoveCrapTask($runVar), + 'ppadditional' => $this->runPostProcessAdditional($runVar), + 'nonamazon' => $this->runNonAmazonTask($runVar), + 'amazon' => $this->runAmazonTask($runVar), + 'scraper' => $this->runIRCScraper($runVar), + default => false, + }; + } + + /** + * Run main task (varies by sequential mode) + */ + protected function runMainTask(int $sequential, array $runVar): bool + { + return match ($sequential) { + 0 => $this->runMainNonSequential($runVar), + 1 => $this->runMainBasic($runVar), + 2 => $this->runMainSequential($runVar), + default => false, + }; + } + + /** + * Run main non-sequential task (binaries, backfill, releases) + */ + protected function runMainNonSequential(array $runVar): bool + { + // This runs in pane 0.1, 0.2, 0.3 + // For now, delegate to existing methods + $this->runBinariesUpdate($runVar); + $this->runBackfill($runVar); + $this->runReleasesUpdate(array_merge($runVar, ['pane' => '0.3'])); + + return true; + } + + /** + * Run main basic sequential task (just releases) + */ + protected function runMainBasic(array $runVar): bool + { + return $this->runReleasesUpdate(array_merge($runVar, ['pane' => '0.1'])); + } + + /** + * Run main full sequential task + */ + protected function runMainSequential(array $runVar): bool + { + // Full sequential mode - runs sequential script + $pane = '0.1'; + $script = base_path('misc/update/nix/tmux/bin/sequential.php'); + + if (! file_exists($script)) { + return $this->disablePane($pane, 'Sequential', 'script not found'); + } + + $niceness = Settings::settingValue('niceness') ?? 2; + $command = "nice -n{$niceness} php {$script}"; + $command = $this->buildCommand($command, ['log_pane' => 'sequential']); + + return $this->paneManager->respawnPane($pane, $command); + } + + /** + * Run fix release names task + */ + protected function runFixNamesTask(array $runVar): bool + { + $enabled = (int) ($runVar['settings']['fix_names'] ?? 0); + $work = (int) ($runVar['counts']['now']['processrenames'] ?? 0); + $pane = '1.0'; + + if ($enabled !== 1) { + return $this->disablePane($pane, 'Fix Release Names', 'disabled in settings'); + } + + if ($work === 0) { + return $this->disablePane($pane, 'Fix Release Names', 'no releases to process'); + } + + $artisan = base_path('artisan'); + $log = $this->getLogFile('fixnames'); + + // Run multiple fix-names passes + $commands = []; + foreach ([3, 5, 7, 9, 11, 13, 15, 17, 19] as $level) { + $commands[] = "php {$artisan} releases:fix-names {$level} --update --category=other --set-status --show 2>&1 | tee -a {$log}"; + } + + $sleep = (int) ($runVar['settings']['fix_timer'] ?? 300); + $allCommands = implode('; ', $commands); + $fullCommand = "{$allCommands}; date +'%Y-%m-%d %T'; sleep {$sleep}"; + + return $this->paneManager->respawnPane($pane, $fullCommand); + } + + /** + * Run remove crap releases task + */ + protected function runRemoveCrapTask(array $runVar): bool + { + $enabled = (int) ($runVar['settings']['fix_crap'] ?? 0); + $pane = '1.1'; + + if ($enabled !== 1) { + return $this->disablePane($pane, 'Remove Crap', 'disabled in settings'); + } + + $option = (int) ($runVar['settings']['fix_crap_opt'] ?? 1); + $script = base_path('misc/update/nix/tmux/bin/removecrap.php'); + + if (! file_exists($script)) { + return $this->disablePane($pane, 'Remove Crap', 'script not found'); + } + + $niceness = Settings::settingValue('niceness') ?? 2; + $command = "nice -n{$niceness} php {$script} {$option}"; + $sleep = (int) ($runVar['settings']['crap_timer'] ?? 300); + $command = $this->buildCommand($command, ['log_pane' => 'removecrap', 'sleep' => $sleep]); + + return $this->paneManager->respawnPane($pane, $command); + } + + /** + * Run post-process additional task + */ + protected function runPostProcessAdditional(array $runVar): bool + { + $enabled = (int) ($runVar['settings']['post'] ?? 0); + $pane = '2.0'; + + if ($enabled !== 1) { + return $this->disablePane($pane, 'Post-process Additional', 'disabled in settings'); + } + + $script = base_path('misc/update/postprocess.php'); + + if (! file_exists($script)) { + return $this->disablePane($pane, 'Post-process Additional', 'script not found'); + } + + $niceness = Settings::settingValue('niceness') ?? 2; + $command = "nice -n{$niceness} php {$script} additional true"; + $sleep = (int) ($runVar['settings']['post_timer'] ?? 300); + $command = $this->buildCommand($command, ['log_pane' => 'post_additional', 'sleep' => $sleep]); + + return $this->paneManager->respawnPane($pane, $command); + } + + /** + * Run non-Amazon post-processing (TV, Movies, Anime) + */ + protected function runNonAmazonTask(array $runVar): bool + { + $enabled = (int) ($runVar['settings']['post_non'] ?? 0); + $pane = '2.1'; + + if ($enabled !== 1) { + return $this->disablePane($pane, 'Post-process Non-Amazon', 'disabled in settings'); + } + + $hasWork = (int) ($runVar['counts']['now']['processmovies'] ?? 0) > 0 + || (int) ($runVar['counts']['now']['processtv'] ?? 0) > 0 + || (int) ($runVar['counts']['now']['processanime'] ?? 0) > 0; + + if (! $hasWork) { + return $this->disablePane($pane, 'Post-process Non-Amazon', 'no movies/tv/anime to process'); + } + + $script = base_path('misc/update/postprocess.php'); + + if (! file_exists($script)) { + return $this->disablePane($pane, 'Post-process Non-Amazon', 'script not found'); + } + + $niceness = Settings::settingValue('niceness') ?? 2; + $log = $this->getLogFile('post_non'); + + $commands = [ + "php {$script} tv true 2>&1 | tee -a {$log}", + "php {$script} movie true 2>&1 | tee -a {$log}", + "php {$script} anime true 2>&1 | tee -a {$log}", + ]; + + $sleep = (int) ($runVar['settings']['post_timer_non'] ?? 300); + $allCommands = "nice -n{$niceness} ".implode('; nice -n{$niceness} php ', $commands); + $fullCommand = "{$allCommands}; date +'%Y-%m-%d %T'; sleep {$sleep}"; + + return $this->paneManager->respawnPane($pane, $fullCommand); + } + + /** + * Run Amazon post-processing (Books, Music, Games, Console, XXX) + */ + protected function runAmazonTask(array $runVar): bool + { + $enabled = (int) ($runVar['settings']['post_amazon'] ?? 0); + $pane = '2.2'; + + if ($enabled !== 1) { + return $this->disablePane($pane, 'Post-process Amazon', 'disabled in settings'); + } + + $hasWork = (int) ($runVar['counts']['now']['processmusic'] ?? 0) > 0 + || (int) ($runVar['counts']['now']['processbooks'] ?? 0) > 0 + || (int) ($runVar['counts']['now']['processconsole'] ?? 0) > 0 + || (int) ($runVar['counts']['now']['processgames'] ?? 0) > 0 + || (int) ($runVar['counts']['now']['processxxx'] ?? 0) > 0; + + if (! $hasWork) { + return $this->disablePane($pane, 'Post-process Amazon', 'no music/books/games to process'); + } + + $script = base_path('misc/update/postprocess.php'); + + if (! file_exists($script)) { + return $this->disablePane($pane, 'Post-process Amazon', 'script not found'); + } + + $niceness = Settings::settingValue('niceness') ?? 2; + $command = "nice -n{$niceness} php {$script} amazon true"; + $sleep = (int) ($runVar['settings']['post_timer_amazon'] ?? 300); + $command = $this->buildCommand($command, ['log_pane' => 'post_amazon', 'sleep' => $sleep]); + + return $this->paneManager->respawnPane($pane, $command); + } +} diff --git a/config/tmux.conf b/config/tmux.conf new file mode 100644 index 000000000..49b0d8511 --- /dev/null +++ b/config/tmux.conf @@ -0,0 +1,45 @@ +# NNTmux Tmux Configuration +# This file is now managed in the config/ directory + +# GNU-Screen compatible prefix +set -g prefix2 C-a +bind C-a send-prefix -2 + +# Set copy mode to use VI keys - Activates PgUp/PgDn buttons +setw -g mode-keys vi + +# Allows for faster key repetition +set -s escape-time 0 + +# Set 256 color display +set -g default-terminal "xterm-256color" + +# Set status bar +set -g status-bg black +set -g status-fg white +set -g status-left "" + +# Constrain window size to the maximum size of any client connected to that window +setw -g aggressive-resize on + +# Activity monitoring +setw -g monitor-activity on + +# Refresh the status bar every 5 seconds +set -g status-interval 5 + +# Right status bar with system information +set -g status-right "#[fg=yellow]#(free -h | grep 'Mem' | awk '{ print \"RAM Used: \"$3\", Cached: \"$6\", \";}')#(free -m | grep 'Swap' | awk '{ print \"Swapped: \"$3;}')M #[fg=cyan,bold] #(uptime | cut -d ',' -f 4-)" + +# Scrollback line buffer per pane +set -g history-limit 6000 + +# Keep pane open after process ends - required for monitor.php re-spawns +set -g remain-on-exit on + +# Enable mouse support +set -g mouse on + +# Rename pane +bind t command-prompt -p "(rename-pane)" -I "#T" "select-pane -T '%%'" + diff --git a/config/tmux.php b/config/tmux.php new file mode 100644 index 000000000..31fceacd3 --- /dev/null +++ b/config/tmux.php @@ -0,0 +1,64 @@ + env('TMUX_CONFIG_FILE', config_path('tmux.conf')), + + 'session' => [ + 'name' => env('TMUX_SESSION_NAME'), + 'default_name' => 'nntmux', + ], + + 'terminal' => [ + 'type' => env('TMUX_TERMINAL', 'xterm-256color'), + 'escape_time' => 0, + ], + + 'status_bar' => [ + 'interval' => 5, + 'bg_color' => 'black', + 'fg_color' => 'white', + 'show_system_info' => true, + ], + + 'panes' => [ + 'history_limit' => 6000, + 'remain_on_exit' => true, + 'aggressive_resize' => true, + 'monitor_activity' => true, + ], + + 'keys' => [ + 'mode' => 'vi', + 'prefix' => 'C-a', + ], + + 'mouse' => [ + 'enabled' => true, + ], + + 'paths' => [ + 'scripts' => base_path('app/Services/Tmux/Scripts'), + 'logs' => storage_path('logs/tmux'), + ], + + 'monitor' => [ + 'delay' => env('TMUX_MONITOR_DELAY', 10), + 'refresh_interval' => env('TMUX_REFRESH_INTERVAL', 60), + ], + + 'performance' => [ + 'niceness' => env('TMUX_NICENESS', 2), + 'timeout' => env('TMUX_TIMEOUT', 300), + ], +]; diff --git a/migrate-templates.php b/migrate-templates.php deleted file mode 100644 index 168358980..000000000 --- a/migrate-templates.php +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env php -\s*\$([a-zA-Z0-9_]+)\}/', '@foreach($$1 as $$2 => $$3)', $bladeContent); - $bladeContent = preg_replace('/\{\/foreach\}/', '@endforeach', $bladeContent); - - // Convert includes - $bladeContent = preg_replace('/\{include\s+file=[\'"](.+?)[\'"]\}/', '@include(\'$1\')', $bladeContent); - - // Convert URLs - keep as is since they're already using Laravel syntax - // {{url()}} and {{route()}} work in both Smarty and Blade - - // Convert Auth checks - $bladeContent = preg_replace('/\{if\s+Auth::check\(\)\}/', '@auth', $bladeContent); - - // Fix object property access: $obj.prop to $obj->prop - $bladeContent = preg_replace('/\$([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)/', '$$$1->$2', $bladeContent); - - return $bladeContent; -} - -if ($argc < 2) { - echo "Usage: php migrate-templates.php [template-file.tpl]\n"; - echo "Example: php migrate-templates.php resources/views/themes/Gentele/browse.tpl\n"; - exit(1); -} - -$inputFile = $argv[1]; - -if (! file_exists($inputFile)) { - echo "Error: File not found: $inputFile\n"; - exit(1); -} - -$smartyContent = file_get_contents($inputFile); -$bladeContent = convertSmartyToBlade($smartyContent); - -// Determine output filename -$outputFile = preg_replace('/\.tpl$/', '.blade.php', $inputFile); -$outputFile = str_replace('/themes/Gentele/', '/', $outputFile); -$outputFile = str_replace('/themes/admin/', '/admin/', $outputFile); -$outputFile = str_replace('/themes/shared/', '/shared/', $outputFile); - -echo "Converting: $inputFile\n"; -echo "To: $outputFile\n"; -echo "\nPreview of converted content:\n"; -echo str_repeat('-', 80)."\n"; -echo substr($bladeContent, 0, 500)."...\n"; -echo str_repeat('-', 80)."\n"; - -echo "\nDo you want to save this conversion? (y/n): "; -$handle = fopen('php://stdin', 'r'); -$line = fgets($handle); -if (trim($line) != 'y') { - echo "Cancelled.\n"; - exit(0); -} - -// Create directory if it doesn't exist -$outputDir = dirname($outputFile); -if (! is_dir($outputDir)) { - mkdir($outputDir, 0755, true); -} - -file_put_contents($outputFile, $bladeContent); -echo "Saved to: $outputFile\n"; -echo "\nNote: Please review the converted file and make manual adjustments as needed.\n"; -echo "Common manual adjustments:\n"; -echo " - Array access: \$arr['key'] instead of \$arr.key\n"; -echo " - Complex expressions may need refinement\n"; -echo " - Bootstrap classes should be converted to TailwindCSS\n"; diff --git a/misc/update/tmux/logs/.gitignore b/misc/update/tmux/logs/.gitignore deleted file mode 100644 index 397b4a762..000000000 --- a/misc/update/tmux/logs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.log diff --git a/misc/update/tmux/scripts/mysqlreport b/misc/update/tmux/scripts/mysqlreport deleted file mode 100644 index b2fa7a47c..000000000 --- a/misc/update/tmux/scripts/mysqlreport +++ /dev/null @@ -1,1295 +0,0 @@ -#!/usr/bin/perl -w - -# mysqlreport v3.5 Apr 16 2008 -# http://hackmysql.com/mysqlreport - -# mysqlreport makes an easy-to-read report of important MySQL status values. -# Copyright 2006-2008 Daniel Nichter -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# The GNU General Public License is available at: -# http://www.gnu.org/copyleft/gpl.html - -use strict; -use File::Temp qw(tempfile); -use DBI; -use Getopt::Long; -eval { require Term::ReadKey; }; -my $RK = ($@ ? 0 : 1); - -sub have_op; - -my $WIN = ($^O eq 'MSWin32' ? 1 : 0); -my %op; -my %mycnf; # ~/.my.cnf -my ($tmpfile_fh, $tmpfile); -my ($stat_name, $stat_val, $stat_label); -my $MySQL_version; -my (%stats, %vars); # SHOW STATUS, SHOW VARIABLES -my (%DMS_vals, %Com_vals, %ib_vals); -my ($dbh, $query); -my ($questions, $key_read_ratio, $key_write_ratio, $dms, $slow_query_t); -my ($key_cache_block_size, $key_buffer_used, $key_buffer_usage); -my ($qc_mem_used, $qc_hi_r, $qc_ip_r); # Query Cache -my $have_innodb_vals; -my ($ib_bp_used, $ib_bp_total, $ib_bp_read_ratio); -my ($relative_live, $relative_infiles); -my $real_uptime; -my (%stats_present, %stats_past); # For relative reports - -GetOptions ( - \%op, - "user=s", - "password:s", - "host=s", - "port=s", - "socket=s", - "no-mycnf", - "infile|in=s", - "outfile=s", - "flush-status", - "email=s", - "r|relative:i", - "c|report-count=i", - "detach", - "help|?", - "debug" -); - -show_help_and_exit() if $op{'help'}; - -get_user_mycnf() unless $op{'no-mycnf'}; - -# Command line options override ~/.my.cnf -$mycnf{'host'} = $op{'host'} if have_op 'host'; -$mycnf{'port'} = $op{'port'} if have_op 'port'; -$mycnf{'socket'} = $op{'socket'} if have_op 'socket'; -$mycnf{'user'} = $op{'user'} if have_op 'user'; - -$mycnf{'user'} ||= $ENV{'USER'}; - -if(exists $op{'password'}) -{ - if($op{'password'} eq '') # Prompt for password - { - Term::ReadKey::ReadMode(2) if $RK; - print "Password for database user $mycnf{'user'}: "; - chomp($mycnf{'pass'} = ); - Term::ReadKey::ReadMode(0), print "\n" if $RK; - } - else { $mycnf{'pass'} = $op{'password'}; } # Use password given on command line -} - -$op{'com'} ||= 3; -$op{'c'} ||= 1; # Used in collect_reports() if --r given integer value - -$relative_live = 0; -$relative_infiles = 0; - -if(defined $op{'r'}) -{ - if($op{r}) { $relative_live = 1; } # if -r was given an integer value - else { $relative_infiles = 1; } -} - -# The report is written to a tmp file first. -# Later it will be moved to $op{'outfile'} or emailed $op{'email'} if needed. -($tmpfile_fh, $tmpfile) = tempfile() or die "Cannot open temporary file for writing: $!\n"; - -if($op{'detach'}) -{ - $SIG{'TERM'} = 'sig_handler'; - - if(fork()) - { - print "mysqlreport has forked and detached.\n"; - print "While running detached, mysqlreport writes reports to '$tmpfile'.\n"; - - exit; - } - - open(STDIN, " $tmpfile") or die "Cannot dup STDOUT: $!\n"; - open(STDERR, "> $tmpfile") or die "Cannot dup STDERR: $!\n"; -} - -select $tmpfile_fh; -$| = 1 if ($op{'detach'} || $relative_live); - -print "tmp file: $tmpfile\n" if $op{debug}; - -# Connect to MySQL -if(!$op{'infile'} && !$relative_infiles) -{ - connect_to_MySQL(); -} - -$have_innodb_vals = 1; # This might be set to 0 later in get_MySQL_version() - -if(defined $op{'r'}) -{ - if($relative_live) - { - print STDERR "mysqlreport is writing relative reports to '$tmpfile'.\n" unless $op{'detach'}; - get_MySQL_version(); - collect_reports(); - } - - if($relative_infiles) { read_relative_infiles(); } -} -else -{ - if(!$op{'infile'}) - { - get_MySQL_version(); - get_vals(); - get_vars(); - } - else - { - read_infile($op{'infile'}); - } - - get_Com_values(); - - set_myisam_vals(); - set_ib_vals() if $have_innodb_vals; - - write_report(); -} - -exit_tasks_and_cleanup(); - -exit; - -# -# Subroutines -# -sub show_help_and_exit -{ - print <<"HELP"; -mysqlreport v3.5 Apr 16 2008 -mysqlreport makes an easy-to-read report of important MySQL status values. - -Command line options (abbreviations work): - --user USER Connect to MySQL as USER - --password PASS Use PASS or prompt for MySQL user's password - --host ADDRESS Connect to MySQL at ADDRESS - --port PORT Connect to MySQL at PORT - --socket SOCKET Connect to MySQL at SOCKET - --no-mycnf Don't read ~/.my.cnf - --infile FILE Read status values from FILE instead of MySQL - --outfile FILE Write report to FILE - --email ADDRESS Email report to ADDRESS (doesn't work on Windows) - --flush-status Issue FLUSH STATUS; after getting current values - --relative X Generate relative reports. If X is an integer, - reports are live from the MySQL server X seconds apart. - If X is a list of infiles (file1 file2 etc.), - reports are generated from the infiles in the order - that they are given. - --report-count N Collect N number of live relative reports (default 1) - --detach Fork and detach from terminal (run in background) - --help Prints this - --debug Print debugging information - -Visit http://hackmysql.com/mysqlreport for more information. -HELP - - exit; -} - -sub get_user_mycnf -{ - print "get_user_mycnf\n" if $op{debug}; - - return if $WIN; - open MYCNF, "$ENV{HOME}/.my.cnf" or return; - while() - { - if(/^(.+?)\s*=\s*"?(.+?)"?\s*$/) - { - $mycnf{$1} = $2; - print "get_user_mycnf: read '$1 = $2'\n" if $op{debug}; - } - } - $mycnf{'pass'} ||= $mycnf{'password'} if exists $mycnf{'password'}; - close MYCNF; -} - -sub connect_to_MySQL -{ - print "connect_to_MySQL\n" if $op{debug}; - - my $dsn; - - if($mycnf{'socket'} && -S $mycnf{'socket'}) - { - $dsn = "DBI:mysql:mysql_socket=$mycnf{socket}"; - } - elsif($mycnf{'host'}) - { - $dsn = "DBI:mysql:host=$mycnf{host}" . ($mycnf{port} ? ";port=$mycnf{port}" : ""); - } - else - { - $dsn = "DBI:mysql:host=localhost"; - } - - print "connect_to_MySQL: DBI DSN: $dsn\n" if $op{debug}; - - $dbh = DBI->connect($dsn, $mycnf{'user'}, $mycnf{'pass'}) or die; -} - -sub collect_reports -{ - print "collect_reports\n" if $op{debug}; - - my $i; - - get_vals(); - get_vars(); - - get_Com_values(); - - %stats_past = %stats; - - set_myisam_vals(); - set_ib_vals() if $have_innodb_vals; - - print "#\n# Beginning report, 0 0:0:0\n#\n"; - - write_report(); - - for($i = 0; $i < $op{'c'}; $i++) - { - $dbh->disconnect(); - - sleep($op{'r'}); - - connect_to_MySQL(); - - print "\n#\n# Interval report " , $i + 1 , ", +", sec_to_dhms(($i + 1) * $op{'r'}), "\n#\n"; - - get_vals(); - - write_relative_report(); - } -} - -sub read_relative_infiles -{ - print "read_relative_infiles\n" if $op{debug}; - - my $slurp; # Used to check infiles for multiple sets of status values - my $n_stats; # Number of multiple sets of status values in an infile - my $infile; - my $report_n; # Report number - - $report_n = 1; - - foreach $infile (@ARGV) - { - # Read all of infile into $slurp - open INFILE, "< $infile" or warn and next; - $slurp = do { local $/; }; - close INFILE; - - $n_stats = 0; - - # Count number of status value sets - $n_stats++ while $slurp =~ /Aborted_clients/g; - - print "read_relative_infiles: found $n_stats sets of status values in file '$infile'\n" - if $op{debug}; - - if($n_stats == 1) - { - read_infile($infile); - relative_infile_report($report_n++); - } - - if($n_stats > 1) - { - my @tmpfile_fh; - my @tmpfile_name; - my $i; - my $stat_n; # Status value set number - - # Create a tmp file for each set of status values - for($i = 0; $i < $n_stats; $i++) - { - my ($fh, $name) = tempfile() - or die "read_relative_infiles: cannot open temporary file for writing: $!\n"; - - push(@tmpfile_fh, $fh); - push(@tmpfile_name, $name); - - print "read_relative_infiles: created tmp file '$name' for set $i\n" if $op{debug}; - } - - $i = 0; - $stat_n = 0; - - select $tmpfile_fh[$i]; - - # Read infile again and copy each set of status values to seperate tmp files - open INFILE, "< $infile" or warn and next; - while() - { - next if /^\+/; - next if /^$/; - - # The infile must begin with the system variable values. - # Therefore, the first occurance of Aborted_clients indicates the beginning - # of the first set of status values if no sets have occured yet ($stat_n == 0). - # In this case, the following status values are printed to the current fh, - # along with the system variable values read thus far, until Aborted_clients - # occurs again. Then begins the second and subsequent sets of status values. - - if(/Aborted_clients/) - { - print and next if $stat_n++ == 0; - select $tmpfile_fh[++$i]; - } - - print; - } - close INFILE; - - # Re-select the main tmp file into which the reports are being written. - select $tmpfile_fh; - - for($i = 0; $i < $n_stats; $i++) - { - close $tmpfile_fh[$i]; - - print "read_relative_infiles: reading set $i tmp file '$tmpfile_name[$i]'\n" - if $op{debug}; - - read_infile($tmpfile_name[$i]); - relative_infile_report($report_n++); - - if($WIN) { `del $tmpfile_name[$i]`; } - else { `rm -f $tmpfile_name[$i]`; } - - print "read_relative_infiles: deleted set $i tmp file '$tmpfile_name[$i]'\n" - if $op{debug}; - } - - } # if($n_stats > 1) - } # foreach $infile (@files) -} - -sub relative_infile_report -{ - print "relative_infile_report\n" if $op{debug}; - - my $report_n = shift; - - if($report_n == 1) - { - get_Com_values(); - - %stats_past = %stats; - - set_myisam_vals(); - set_ib_vals() if $have_innodb_vals; - - print "#\n# Beginning report, 0 0:0:0\n#\n"; - - write_report(); - } - else - { - print "\n#\n# Interval report ", $report_n - 1, ", +", - sec_to_dhms($stats{Uptime} - $stats_past{Uptime}), - "\n#\n"; - - write_relative_report(); - } -} - -sub get_vals -{ - print "get_vals\n" if $op{debug}; - - my @row; - - # Get status values - if($MySQL_version >= 50002) - { - $query = $dbh->prepare("SHOW GLOBAL STATUS;"); - } - else - { - $query = $dbh->prepare("SHOW STATUS;"); - } - $query->execute(); - while(@row = $query->fetchrow_[]) { $stats{$row[0]} = $row[1]; } - - $real_uptime = $stats{'Uptime'}; -} - -sub get_vars -{ - print "get_vars\n" if $op{debug}; - - my @row; - - # Get server system variables - $query = $dbh->prepare("SHOW VARIABLES;"); - $query->execute(); - while(@row = $query->fetchrow_[]) { $vars{$row[0]} = $row[1]; } - - # table_cache was renamed to table_open_cache in MySQL 5.1.3 - if($MySQL_version >= 50103) - { - $vars{'table_cache'} = $vars{'table_open_cache'}; - } -} - -sub read_infile -{ - print "read_infile\n" if $op{debug}; - - my $infile = shift; - - # Default required system variable values if not set in INFILE. - # As of mysqlreport v3.5 the direct output from SHOW VARIABLES; - # can be put into INFILE instead. See http://hackmysql.com/mysqlreportdoc - # for details. - $vars{'version'} = "0.0.0" if !exists $vars{'version'}; - $vars{'table_cache'} = 64 if !exists $vars{'table_cache'}; - $vars{'max_connections'} = 100 if !exists $vars{'max_connections'}; - $vars{'key_buffer_size'} = 8388600 if !exists $vars{'key_buffer_size'}; # 8M - $vars{'thread_cache_size'} = 0 if !exists $vars{'thread_cache_size'}; - $vars{'tmp_table_size'} = 0 if !exists $vars{'tmp_table_size'}; - $vars{'long_query_time'} = '?' if !exists $vars{'long_query_time'}; - $vars{'log_slow_queries'} = '?' if !exists $vars{'log_slow_queries'}; - - # One should also add: - # key_cache_block_size - # query_cache_size - # to INFILE if needed. - - open INFILE, "< $infile" or die "Cannot open INFILE '$infile': $!\n"; - - while() - { - last if !defined $_; - - next if /^\+/; # skip divider lines - next if /^$/; # skip blank lines - - next until /(Aborted_clients|back_log|=)/; - - if($1 eq 'Aborted_clients') # status values - { - print "read_infile: start stats\n" if $op{debug}; - - while($_) - { - chomp; - if(/([A-Za-z_]+)[\s\t|]+(\d+)/) - { - $stats{$1} = $2; - print "read_infile: save $1 = $2\n" if $op{debug}; - } - else { print "read_infile: ignore '$_'\n" if $op{debug}; } - - last if $1 eq 'Uptime'; # exit while() if end of status values - $_ = ; # otherwise, read next line of status values - } - } - elsif($1 eq 'back_log') # system variable values - { - print "read_infile: start vars\n" if $op{debug}; - - while($_) - { - chomp; - if(/([A-Za-z_]+)[\s\t|]+([\w\.\-]+)/) # This will exclude some vars - { # like pid_file which we don't need - $vars{$1} = $2; - print "read_infile: save $1 = $2\n" if $op{debug}; - } - else { print "read_infile: ignore '$_'\n" if $op{debug}; } - - last if $1 eq 'wait_timeout'; # exit while() if end of vars - $_ = ; # otherwise, read next line of vars - } - } - elsif($1 eq '=') # old style, manually added system variable values - { - print "read_infile: start old vars\n" if $op{debug}; - - while($_ && $_ =~ /=/) - { - chomp; - if(/^\s*(\w+)\s*=\s*([0-9.]+)(M*)\s*$/) # e.g.: key_buffer_size = 128M - { - $vars{$1} = ($3 ? $2 * 1024 * 1024 : $2); - print "read_infile: read '$_' as $1 = $vars{$1}\n" if $op{debug}; - } - else { print "read_infile: ignore '$_'\n" if $op{debug}; } - - $_ = ; # otherwise, read next line of old vars - } - - redo; - } - else - { - print "read_infile: unrecognized line: '$_'\n" if $op{debug}; - } - } - - close INFILE; - - $real_uptime = $stats{'Uptime'}; - - $vars{'table_cache'} = $vars{'table_open_cache'} if exists $vars{'table_open_cache'}; - - get_MySQL_version(); -} - -sub get_MySQL_version -{ - print "get_MySQL_version\n" if $op{debug}; - - return if $MySQL_version; - - my ($major, $minor, $patch); - - if($op{'infile'} || $relative_infiles) - { - ($major, $minor, $patch) = ($vars{'version'} =~ /(\d{1,2})\.(\d{1,2})\.(\d{1,2})/); - } - else - { - my @row; - - $query = $dbh->prepare("SHOW VARIABLES LIKE 'version';"); - $query->execute(); - @row = $query->fetchrow_[]; - ($major, $minor, $patch) = ($row[1] =~ /(\d{1,2})\.(\d{1,2})\.(\d{1,2})/); - } - - $MySQL_version = sprintf("%d%02d%02d", $major, $minor, $patch); - - # Innodb_ status values were added in 5.0.2 - if($MySQL_version < 50002) - { - $have_innodb_vals = 0; - print "get_MySQL_version: no InnoDB reports because MySQL version is older than 5.0.2\n" if $op{debug}; - } -} - -sub set_myisam_vals -{ - print "set_myisam_vals\n" if $op{debug}; - - $questions = $stats{'Questions'}; - - $key_read_ratio = sprintf "%.2f", - ($stats{'Key_read_requests'} ? - 100 - ($stats{'Key_reads'} / $stats{'Key_read_requests'}) * 100 : - 0); - - $key_write_ratio = sprintf "%.2f", - ($stats{'Key_write_requests'} ? - 100 - ($stats{'Key_writes'} / $stats{'Key_write_requests'}) * 100 : - 0); - - $key_cache_block_size = (defined $vars{'key_cache_block_size'} ? - $vars{'key_cache_block_size'} : - 1024); - - $key_buffer_used = $stats{'Key_blocks_used'} * $key_cache_block_size; - - if(defined $stats{'Key_blocks_unused'}) # MySQL 4.1.2+ - { - $key_buffer_usage = $vars{'key_buffer_size'} - - ($stats{'Key_blocks_unused'} * $key_cache_block_size); - } - else { $key_buffer_usage = -1; } - - # Data Manipulation Statements: http://dev.mysql.com/doc/refman/5.0/en/data-manipulation.html - %DMS_vals = - ( - SELECT => $stats{'Com_select'}, - INSERT => $stats{'Com_insert'} + $stats{'Com_insert_select'}, - REPLACE => $stats{'Com_replace'} + $stats{'Com_replace_select'}, - UPDATE => $stats{'Com_update'} + - (exists $stats{'Com_update_multi'} ? $stats{'Com_update_multi'} : 0), - DELETE => $stats{'Com_delete'} + - (exists $stats{'Com_delete_multi'} ? $stats{'Com_delete_multi'} : 0) - ); - - $dms = $DMS_vals{SELECT} + $DMS_vals{INSERT} + $DMS_vals{REPLACE} + $DMS_vals{UPDATE} + $DMS_vals{DELETE}; - - $slow_query_t = format_u_time($vars{long_query_time}); - -} - -sub set_ib_vals -{ - print "set_ib_vals\n" if $op{debug}; - - $ib_bp_used = ($stats{'Innodb_buffer_pool_pages_total'} - - $stats{'Innodb_buffer_pool_pages_free'}) * - $stats{'Innodb_page_size'}; - - $ib_bp_total = $stats{'Innodb_buffer_pool_pages_total'} * $stats{'Innodb_page_size'}; - - $ib_bp_read_ratio = sprintf "%.2f", - ($stats{'Innodb_buffer_pool_read_requests'} ? - 100 - ($stats{'Innodb_buffer_pool_reads'} / - $stats{'Innodb_buffer_pool_read_requests'}) * 100 : - 0); -} - -sub write_relative_report -{ - print "write_relative_report\n" if $op{debug}; - - %stats_present = %stats; - - for(keys %stats) - { - if($stats_past{$_} =~ /\d+/) - { - if($stats_present{$_} >= $stats_past{$_}) # Avoid negative values - { - $stats{$_} = $stats_present{$_} - $stats_past{$_}; - } - } - } - - # These values are either "at present" or "high water marks". - # Therefore, it is more logical to not relativize these values. - # Doing otherwise causes strange and misleading values. - $stats{'Key_blocks_used'} = $stats_present{'Key_blocks_used'}; - $stats{'Open_tables'} = $stats_present{'Open_tables'}; - $stats{'Max_used_connections'} = $stats_present{'Max_used_connections'}; - $stats{'Threads_running'} = $stats_present{'Threads_running'}; - $stats{'Threads_connected'} = $stats_present{'Threads_connected'}; - $stats{'Threads_cached'} = $stats_present{'Threads_cached'}; - $stats{'Qcache_free_blocks'} = $stats_present{'Qcache_free_blocks'}; - $stats{'Qcache_total_blocks'} = $stats_present{'Qcache_total_blocks'}; - $stats{'Qcache_free_memory'} = $stats_present{'Qcache_free_memory'}; - if($have_innodb_vals) - { - $stats{'Innodb_page_size'} = $stats_present{'Innodb_page_size'}; - $stats{'Innodb_buffer_pool_pages_data'} = $stats_present{'Innodb_buffer_pool_pages_data'}; - $stats{'Innodb_buffer_pool_pages_dirty'} = $stats_present{'Innodb_buffer_pool_pages_dirty'}; - $stats{'Innodb_buffer_pool_pages_free'} = $stats_present{'Innodb_buffer_pool_pages_free'}; - $stats{'Innodb_buffer_pool_pages_latched'} = $stats_present{'Innodb_buffer_pool_pages_latched'}; - $stats{'Innodb_buffer_pool_pages_misc'} = $stats_present{'Innodb_buffer_pool_pages_misc'}; - $stats{'Innodb_buffer_pool_pages_total'} = $stats_present{'Innodb_buffer_pool_pages_total'}; - $stats{'Innodb_data_pending_fsyncs'} = $stats_present{'Innodb_data_pending_fsyncs'}; - $stats{'Innodb_data_pending_reads'} = $stats_present{'Innodb_data_pending_reads'}; - $stats{'Innodb_data_pending_writes'} = $stats_present{'Innodb_data_pending_writes'}; - - # Innodb_row_lock_ values were added in MySQL 5.0.3 - if($MySQL_version >= 50003) - { - $stats{'Innodb_row_lock_current_waits'} = $stats_present{'Innodb_row_lock_current_waits'}; - $stats{'Innodb_row_lock_time_avg'} = $stats_present{'Innodb_row_lock_time_avg'}; - $stats{'Innodb_row_lock_time_max'} = $stats_present{'Innodb_row_lock_time_max'}; - } - } - - get_Com_values(); - - %stats_past = %stats_present; - - set_myisam_vals(); - set_ib_vals() if $have_innodb_vals; - - write_report(); -} - -sub write_report -{ - print "write_report\n" if $op{debug}; - - $~ = 'MYSQL_TIME', write; - $~ = 'KEY_BUFF_MAX', write; - if($key_buffer_usage != -1) { $~ = 'KEY_BUFF_USAGE', write } - $~ = 'KEY_RATIOS', write; - write_DTQ(); - $~ = 'SLOW_DMS', write; - write_DMS(); - write_Com(); - $~ = 'SAS', write; - write_qcache(); - $~ = 'REPORT_END', write; - $~ = 'TAB', write; - - write_InnoDB() if $have_innodb_vals; -} - -sub sec_to_dhms # Seconds to days hours:minutes:seconds -{ - my $s = shift; - my ($d, $h, $m) = (0, 0, 0); - - return '0 0:0:0' if $s <= 0; - - if($s >= 86400) - { - $d = int $s / 86400; - $s -= $d * 86400; - } - - if($s >= 3600) - { - $h = int $s / 3600; - $s -= $h * 3600; - } - - $m = int $s / 60; - $s -= $m * 60; - - return "$d $h:$m:$s"; -} - -sub make_short -{ - my ($number, $kb, $d) = @_; - my $n = 0; - my $short; - - $d ||= 2; - - if($kb) { while ($number > 1023) { $number /= 1024; $n++; }; } - else { while ($number > 999) { $number /= 1000; $n++; }; } - - $short = sprintf "%.${d}f%s", $number, ('','k','M','G','T')[$n]; - if($short =~ /^(.+)\.(00)$/) { return $1; } # 12.00 -> 12 but not 12.00k -> 12k - - return $short; -} - -# What began as a simple but great idea has become the new standard: -# long_query_time in microseconds. For MySQL 5.1.21+ and 6.0.4+ this -# is now standard. For 4.1 and 5.0 patches, the architects of this -# idea provide: http://www.mysqlperformanceblog.com/mysql-patches/ -# Relevant notes in MySQL manual: -# http://dev.mysql.com/doc/refman/5.1/en/slow-query-log.html -# http://dev.mysql.com/doc/refman/6.0/en/slow-query-log.html -# -# The format_u_time sub simply beautifies long_query_time. - -sub format_u_time # format microsecond (�) time value -{ - # 0.000000 - 0.000999 = 0 - 999 � - # 0.001000 - 0.999999 = 1 ms - 999.999 ms - # 1.000000 - n.nnnnnn = 1 s - n.nnnnn s - - my $t = shift; - my $f; # formatted � time - my $u = chr(($WIN ? 230 : 181)); - - $t = 0 if $t < 0; - - if($t > 0 && $t <= 0.000999) - { - $f = ($t * 1000000) . " $u"; - } - elsif($t >= 0.001000 && $t <= 0.999999) - { - $f = ($t * 1000) . ' ms'; - } - elsif($t >= 1) - { - $f = ($t * 1) . ' s'; # * 1 to remove insignificant zeros - } - else - { - $f = 0; # $t should = 0 at this point - } - - return $f; -} - -sub perc # Percentage -{ - my($is, $of) = @_; - return sprintf "%.2f", ($is * 100) / ($of ||= 1); -} - -sub t # Time average per second -{ - my $val = shift; - return 0 if !$val; - return(make_short($val / $stats{'Uptime'}, 0, 1)); -} - -sub email_report # Email given report to $op{'email'} -{ - print "email_report\n" if $op{debug}; - - return if $WIN; - - my $report = shift; - - open SENDMAIL, "|/usr/sbin/sendmail -t"; - print SENDMAIL "From: mysqlreport\n"; - print SENDMAIL "To: $op{email}\n"; - print SENDMAIL "Subject: MySQL status report on " . ($mycnf{'host'} || 'localhost') . "\n\n"; - print SENDMAIL `cat $report`; - close SENDMAIL; -} - -sub cat_report # Print given report to screen -{ - print "cat_report\n" if $op{debug}; - - my $report = shift; - my @report; - - open REPORT, "< $report"; - @report = ; - close REPORT; - print @report; -} - -sub get_Com_values -{ - print "get_Com_values\n" if $op{debug}; - - %Com_vals = (); - - # Make copy of just the Com_ values - for(keys %stats) - { - if(grep /^Com_/, $_ and $stats{$_} > 0) - { - /^Com_(.*)/; - $Com_vals{$1} = $stats{$_}; - } - } - - # Remove DMS values - delete $Com_vals{'select'}; - delete $Com_vals{'insert'}; - delete $Com_vals{'insert_select'}; - delete $Com_vals{'replace'}; - delete $Com_vals{'replace_select'}; - delete $Com_vals{'update'}; - delete $Com_vals{'update_multi'} if exists $Com_vals{'update_multi'}; - delete $Com_vals{'delete'}; - delete $Com_vals{'delete_multi'} if exists $Com_vals{'delete_multi'}; -} - -sub write_DTQ # Write DTQ report in descending order by values -{ - print "write_DTQ\n" if $op{debug}; - - $~ = 'DTQ'; - - my %DTQ; - my $first = 1; - - # Total Com values - $stat_val = 0; - for(values %Com_vals) { $stat_val += $_; } - $DTQ{'Com_'} = $stat_val; - - $DTQ{'DMS'} = $dms; - $DTQ{'QC Hits'} = $stats{'Qcache_hits'} if $stats{'Qcache_hits'} != 0; - $DTQ{'COM_QUIT'} = int (($stats{'Connections'} - 2) - ($stats{'Aborted_clients'} / 2)); - - $stat_val = 0; - for(values %DTQ) { $stat_val += $_; } - if($questions != $stat_val) - { - $DTQ{($questions > $stat_val ? '+Unknown' : '-Unknown')} = abs $questions - $stat_val; - } - - for(sort { $DTQ{$b} <=> $DTQ{$a} } keys(%DTQ)) - { - if($first) { $stat_label = '%Total:'; $first = 0; } - else { $stat_label = ''; } - - $stat_name = $_; - $stat_val = $DTQ{$_}; - write; - } -} - -sub write_DMS # Write DMS report in descending order by values -{ - print "write_DMS\n" if $op{debug}; - - $~ = 'DMS'; - - for(sort { $DMS_vals{$b} <=> $DMS_vals{$a} } keys(%DMS_vals)) - { - $stat_name = $_; - $stat_val = $DMS_vals{$_}; - write; - } -} - -sub write_Com # Write COM report in descending order by values -{ - print "write_Com\n" if $op{debug}; - - my $i = $op{'com'}; - - $~ = 'COM_1'; - - # Total Com values and write first line of COM report - $stat_label = '%Total:' unless $op{'dtq'}; - $stat_val = 0; - for(values %Com_vals) { $stat_val += $_; } - write; - - $~ = 'COM_2'; - - # Sort remaining Com values, print only the top $op{'com'} number of values - for(sort { $Com_vals{$b} <=> $Com_vals{$a} } keys(%Com_vals)) - { - $stat_name = $_; - $stat_val = $Com_vals{$_}; - write; - - last if !(--$i); - } -} - -sub write_qcache -{ - print "write_qcache\n" if $op{debug}; - - # Query cache was added in 4.0.1, but have_query_cache was added in 4.0.2, - # ergo this method is slightly more reliable - return if not exists $vars{'query_cache_size'}; - return if $vars{'query_cache_size'} == 0; - - $qc_mem_used = $vars{'query_cache_size'} - $stats{'Qcache_free_memory'}; - $qc_hi_r = sprintf "%.2f", $stats{'Qcache_hits'} / ($stats{'Qcache_inserts'} ||= 1); - $qc_ip_r = sprintf "%.2f", $stats{'Qcache_inserts'} / ($stats{'Qcache_lowmem_prunes'} ||= 1); - - $~ = 'QCACHE'; - write; -} - -sub write_InnoDB -{ - print "write_InnoDB\n" if $op{debug}; - - return if not defined $stats{'Innodb_page_size'}; - - $~ = 'IB'; - write; - - # Innodb_row_lock_ values were added in MySQL 5.0.3 - if($MySQL_version >= 50003) - { - $~ = 'IB_LOCK'; - write; - } - - # Data, Pages, Rows - $~ = 'IB_DPR'; - write; -} - -sub have_op -{ - my $key = shift; - return 1 if (exists $op{$key} && $op{$key} ne ''); - return 0; -} - -sub sig_handler -{ - print "\nReceived signal at " , scalar localtime , "\n"; - exit_tasks_and_cleanup(); - exit; -} - -sub exit_tasks_and_cleanup -{ - print "exit_tasks_and_cleanup\n" if $op{debug}; - - close $tmpfile_fh; - select STDOUT unless $op{'detach'}; - - email_report($tmpfile) if $op{'email'}; - - cat_report($tmpfile) unless $op{'detach'}; - - if($op{'outfile'}) - { - if($WIN) { `move $tmpfile $op{outfile}`; } - else { `mv $tmpfile $op{outfile}`; } - } - else - { - if($WIN) { `del $tmpfile`; } - else { `rm -f $tmpfile`; } - } - - if(!$op{'infile'} && !$relative_infiles) - { - if($op{'flush-status'}) - { - $query = $dbh->prepare("FLUSH STATUS;"); - $query->execute(); - } - - $query->finish(); - $dbh->disconnect(); - } -} - -# -# Formats -# - -format MYSQL_TIME = -MySQL @<<<<<<<<<<<<<<<< uptime @<<<<<<<<<<< @>>>>>>>>>>>>>>>>>>>>>>>> -$vars{'version'}, sec_to_dhms($real_uptime), (($op{infile} || $relative_infiles) ? '' : scalar localtime) -. - -format KEY_BUFF_MAX = - -__ Key _________________________________________________________________ -Buffer used @>>>>>> of @>>>>>> %Used: @>>>>> -make_short($key_buffer_used, 1), make_short($vars{'key_buffer_size'}, 1), perc($key_buffer_used, $vars{'key_buffer_size'}) -. - -format KEY_BUFF_USAGE = - Current @>>>>>> %Usage: @>>>>> -make_short($key_buffer_usage, 1), perc($key_buffer_usage, $vars{'key_buffer_size'}) -. - -format KEY_RATIOS = -Write hit @>>>>>% -$key_write_ratio -Read hit @>>>>>% -$key_read_ratio - -__ Questions ___________________________________________________________ -Total @>>>>>>>> @>>>>>/s -make_short($questions), t($questions) -. - -format DTQ = - @<<<<<<< @>>>>>>>> @>>>>>/s @>>>>>> @>>>>> -$stat_name, make_short($stat_val), t($stat_val), $stat_label, perc($stat_val, $questions) -. - -format SLOW_DMS = -Slow @<<<<<<< @>>>>>> @>>>>>/s @>>>>> %DMS: @>>>>> Log: @>> -$slow_query_t, make_short($stats{'Slow_queries'}), t($stats{'Slow_queries'}), perc($stats{'Slow_queries'}, $questions), perc($stats{'Slow_queries'}, $dms), $vars{'log_slow_queries'} -DMS @>>>>>>>> @>>>>>/s @>>>>> -make_short($dms), t($dms), perc($dms, $questions) -. - -format DMS = - @<<<<<<< @>>>>>>>> @>>>>>/s @>>>>> @>>>>> -$stat_name, make_short($stat_val), t($stat_val), perc($stat_val, $questions), perc($stat_val, $dms) -. - -format COM_1 = -Com_ @>>>>>>>> @>>>>>/s @>>>>> -make_short($stat_val), t($stat_val), perc($stat_val, $questions) -. - -format COM_2 = - @<<<<<<<<<< @>>>>>> @>>>>>/s @>>>>> -$stat_name, make_short($stat_val), t($stat_val), perc($stat_val, $questions) -. - -format SAS = - -__ SELECT and Sort _____________________________________________________ -Scan @>>>>>> @>>>>/s %SELECT: @>>>>> -make_short($stats{'Select_scan'}), t($stats{'Select_scan'}), perc($stats{'Select_scan'}, $stats{'Com_select'}) -Range @>>>>>> @>>>>/s @>>>>> -make_short($stats{'Select_range'}), t($stats{'Select_range'}), perc($stats{'Select_range'}, $stats{'Com_select'}) -Full join @>>>>>> @>>>>/s @>>>>> -make_short($stats{'Select_full_join'}), t($stats{'Select_full_join'}), perc($stats{'Select_full_join'}, $stats{'Com_select'}) -Range check @>>>>>> @>>>>/s @>>>>> -make_short($stats{'Select_range_check'}), t($stats{'Select_range_check'}), perc($stats{'Select_range_check'}, $stats{'Com_select'}) -Full rng join @>>>>>> @>>>>/s @>>>>> -make_short($stats{'Select_full_range_join'}), t($stats{'Select_full_range_join'}), perc($stats{'Select_full_range_join'}, $stats{'Com_select'}) -Sort scan @>>>>>> @>>>>/s -make_short($stats{'Sort_scan'}), t($stats{'Sort_scan'}) -Sort range @>>>>>> @>>>>/s -make_short($stats{'Sort_range'}), t($stats{'Sort_range'}) -Sort mrg pass @>>>>>> @>>>>/s -make_short($stats{'Sort_merge_passes'}), t($stats{'Sort_merge_passes'}) -. - -format QCACHE = - -__ Query Cache _________________________________________________________ -Memory usage @>>>>>> of @>>>>>> %Used: @>>>>> -make_short($qc_mem_used, 1), make_short($vars{'query_cache_size'}, 1), perc($qc_mem_used, $vars{'query_cache_size'}) -Block Fragmnt @>>>>>% -perc($stats{'Qcache_free_blocks'}, $stats{'Qcache_total_blocks'}) -Hits @>>>>>> @>>>>/s -make_short($stats{'Qcache_hits'}), t($stats{'Qcache_hits'}) -Inserts @>>>>>> @>>>>/s -make_short($stats{'Qcache_inserts'}), t($stats{'Qcache_inserts'}) -Insrt:Prune @>>>>>>:1 @>>>>/s -make_short($qc_ip_r), t($stats{'Qcache_inserts'} - $stats{'Qcache_lowmem_prunes'}) -Hit:Insert @>>>>>>:1 -$qc_hi_r, t($qc_hi_r) -. - -# Not really the end... -format REPORT_END = - -__ Table Locks _________________________________________________________ -Waited @>>>>>>>> @>>>>>/s %Total: @>>>>> -make_short($stats{'Table_locks_waited'}), t($stats{'Table_locks_waited'}), perc($stats{'Table_locks_waited'}, $stats{'Table_locks_waited'} + $stats{'Table_locks_immediate'}); -Immediate @>>>>>>>> @>>>>>/s -make_short($stats{'Table_locks_immediate'}), t($stats{'Table_locks_immediate'}) - -__ Tables ______________________________________________________________ -Open @>>>>>>>> of @>>> %Cache: @>>>>> -$stats{'Open_tables'}, $vars{'table_cache'}, perc($stats{'Open_tables'}, $vars{'table_cache'}) -Opened @>>>>>>>> @>>>>>/s -make_short($stats{'Opened_tables'}), t($stats{'Opened_tables'}) - -__ Connections _________________________________________________________ -Max used @>>>>>>>> of @>>> %Max: @>>>>> -$stats{'Max_used_connections'}, $vars{'max_connections'}, perc($stats{'Max_used_connections'}, $vars{'max_connections'}) -Total @>>>>>>>> @>>>>>/s -make_short($stats{'Connections'}), t($stats{'Connections'}) - -__ Created Temp ________________________________________________________ -Disk table @>>>>>>>> @>>>>>/s -make_short($stats{'Created_tmp_disk_tables'}), t($stats{'Created_tmp_disk_tables'}) -Table @>>>>>>>> @>>>>>/s Size: @>>>>> -make_short($stats{'Created_tmp_tables'}), t($stats{'Created_tmp_tables'}), make_short($vars{'tmp_table_size'}, 1, 1) -File @>>>>>>>> @>>>>>/s -make_short($stats{'Created_tmp_files'}), t($stats{'Created_tmp_files'}) -. - -format TAB = - -__ Threads _____________________________________________________________ -Running @>>>>>>>> of @>>> -$stats{'Threads_running'}, $stats{'Threads_connected'} -Cached @>>>>>>>> of @>>> %Hit: @>>>>> -$stats{'Threads_cached'}, $vars{'thread_cache_size'}, make_short(100 - perc($stats{'Threads_created'}, $stats{'Connections'})) -Created @>>>>>>>> @>>>>>/s -make_short($stats{'Threads_created'}), t($stats{'Threads_created'}) -Slow @>>>>>>>> @>>>>>/s -$stats{'Slow_launch_threads'}, t($stats{'Slow_launch_threads'}) - -__ Aborted _____________________________________________________________ -Clients @>>>>>>>> @>>>>>/s -make_short($stats{'Aborted_clients'}), t($stats{'Aborted_clients'}) -Connects @>>>>>>>> @>>>>>/s -make_short($stats{'Aborted_connects'}), t($stats{'Aborted_connects'}) - -__ Bytes _______________________________________________________________ -Sent @>>>>>>>> @>>>>>/s -make_short($stats{'Bytes_sent'}), t($stats{'Bytes_sent'}) -Received @>>>>>>>> @>>>>>/s -make_short($stats{'Bytes_received'}), t($stats{'Bytes_received'}) -. - -format IB = - -__ InnoDB Buffer Pool __________________________________________________ -Usage @>>>>>> of @>>>>>> %Used: @>>>>> -make_short($ib_bp_used, 1), make_short($ib_bp_total, 1), perc($ib_bp_used, $ib_bp_total) -Read hit @>>>>>% -$ib_bp_read_ratio; -Pages - Free @>>>>>>>> %Total: @>>>>> -make_short($stats{'Innodb_buffer_pool_pages_free'}), perc($stats{'Innodb_buffer_pool_pages_free'}, $stats{'Innodb_buffer_pool_pages_total'}) - Data @>>>>>>>> @>>>>> %Drty: @>>>>> -make_short($stats{'Innodb_buffer_pool_pages_data'}), perc($stats{'Innodb_buffer_pool_pages_data'}, $stats{'Innodb_buffer_pool_pages_total'}), perc($stats{'Innodb_buffer_pool_pages_dirty'}, $stats{'Innodb_buffer_pool_pages_data'}) - Misc @>>>>>>>> @>>>>> - $stats{'Innodb_buffer_pool_pages_misc'}, perc($stats{'Innodb_buffer_pool_pages_misc'}, $stats{'Innodb_buffer_pool_pages_total'}) - Latched @>>>>>>>> @>>>>> -$stats{'Innodb_buffer_pool_pages_latched'}, perc($stats{'Innodb_buffer_pool_pages_latched'}, $stats{'Innodb_buffer_pool_pages_total'}) -Reads @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_buffer_pool_read_requests'}), t($stats{'Innodb_buffer_pool_read_requests'}) - From file @>>>>>>>> @>>>>>/s @>>>>> -make_short($stats{'Innodb_buffer_pool_reads'}), t($stats{'Innodb_buffer_pool_reads'}), perc($stats{'Innodb_buffer_pool_reads'}, $stats{'Innodb_buffer_pool_read_requests'}) - Ahead Rnd @>>>>>>>> @>>>>>/s -$stats{'Innodb_buffer_pool_read_ahead_rnd'}, t($stats{'Innodb_buffer_pool_read_ahead_rnd'}) - Ahead Sql @>>>>>>>> @>>>>>/s -$stats{'Innodb_buffer_pool_read_ahead_seq'}, t($stats{'Innodb_buffer_pool_read_ahead_seq'}) -Writes @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_buffer_pool_write_requests'}), t($stats{'Innodb_buffer_pool_write_requests'}) -Flushes @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_buffer_pool_pages_flushed'}), t($stats{'Innodb_buffer_pool_pages_flushed'}) -Wait Free @>>>>>>>> @>>>>>/s -$stats{'Innodb_buffer_pool_wait_free'}, t($stats{'Innodb_buffer_pool_wait_free'}) -. - -format IB_LOCK = - -__ InnoDB Lock _________________________________________________________ -Waits @>>>>>>>> @>>>>>/s -$stats{'Innodb_row_lock_waits'}, t($stats{'Innodb_row_lock_waits'}) -Current @>>>>>>>> -$stats{'Innodb_row_lock_current_waits'} -Time acquiring - Total @>>>>>>>> ms -$stats{'Innodb_row_lock_time'} - Average @>>>>>>>> ms -$stats{'Innodb_row_lock_time_avg'} - Max @>>>>>>>> ms -$stats{'Innodb_row_lock_time_max'} -. - -format IB_DPR = - -__ InnoDB Data, Pages, Rows ____________________________________________ -Data - Reads @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_data_reads'}), t($stats{'Innodb_data_reads'}) - Writes @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_data_writes'}), t($stats{'Innodb_data_writes'}) - fsync @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_data_fsyncs'}), t($stats{'Innodb_data_fsyncs'}) - Pending - Reads @>>>>>>>> -$stats{'Innodb_data_pending_reads'}, t($stats{'Innodb_data_pending_reads'}) - Writes @>>>>>>>> -$stats{'Innodb_data_pending_writes'}, t($stats{'Innodb_data_pending_writes'}) - fsync @>>>>>>>> -$stats{'Innodb_data_pending_fsyncs'}, t($stats{'Innodb_data_pending_fsyncs'}) - -Pages - Created @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_pages_created'}), t($stats{'Innodb_pages_created'}) - Read @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_pages_read'}), t($stats{'Innodb_pages_read'}) - Written @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_pages_written'}), t($stats{'Innodb_pages_written'}) - -Rows - Deleted @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_rows_deleted'}), t($stats{'Innodb_rows_deleted'}) - Inserted @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_rows_inserted'}), t($stats{'Innodb_rows_inserted'}) - Read @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_rows_read'}), t($stats{'Innodb_rows_read'}) - Updated @>>>>>>>> @>>>>>/s -make_short($stats{'Innodb_rows_updated'}), t($stats{'Innodb_rows_updated'}) -. diff --git a/misc/update/tmux/scripts/mysqltuner.pl b/misc/update/tmux/scripts/mysqltuner.pl deleted file mode 100644 index a8bf24f3b..000000000 --- a/misc/update/tmux/scripts/mysqltuner.pl +++ /dev/null @@ -1,7748 +0,0 @@ -#!/usr/bin/env perl -# mysqltuner.pl - Version 2.6.2 -# High Performance MySQL Tuning Script -# Copyright (C) 2015-2023 Jean-Marie Renouard - jmrenouard@gmail.com -# Copyright (C) 2006-2023 Major Hayden - major@mhtx.net - -# For the latest updates, please visit http://mysqltuner.pl/ -# Git repository available at https://github.com/major/MySQLTuner-perl -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# This project would not be possible without help from: -# Matthew Montgomery Paul Kehrer Dave Burgess -# Jonathan Hinds Mike Jackson Nils Breunese -# Shawn Ashlee Luuk Vosslamber Ville Skytta -# Trent Hornibrook Jason Gill Mark Imbriaco -# Greg Eden Aubin Galinotti Giovanni Bechis -# Bill Bradford Ryan Novosielski Michael Scheidell -# Blair Christensen Hans du Plooy Victor Trac -# Everett Barnes Tom Krouper Gary Barrueto -# Simon Greenaway Adam Stein Isart Montane -# Baptiste M. Cole Turner Major Hayden -# Joe Ashcraft Jean-Marie Renouard Christian Loos -# Julien Francoz Daniel Black Long Radix -# -# Inspired by Matthew Montgomery's tuning-primer.sh script: -# http://www.day32.com/MySQL/ -# -package main; - -use 5.005; -use strict; -use warnings; - -use diagnostics; -use File::Spec; -use Getopt::Long; -use Pod::Usage; -use File::Basename; -use Cwd 'abs_path'; - -#use Data::Dumper; -#$Data::Dumper::Pair = " : "; - -# for which() -#use Env; - -# Set up a few variables for use in the script -my $tunerversion = "2.6.2"; -my ( @adjvars, @generalrec ); - -# Set defaults -my %opt = ( - "silent" => 0, - "nobad" => 0, - "nogood" => 0, - "noinfo" => 0, - "debug" => 0, - "nocolor" => ( !-t STDOUT ), - "color" => ( -t STDOUT ), - "forcemem" => 0, - "forceswap" => 0, - "host" => 0, - "socket" => 0, - "port" => 0, - "user" => 0, - "pass" => 0, - "password" => 0, - "ssl-ca" => 0, - "skipsize" => 0, - "checkversion" => 0, - "updateversion" => 0, - "buffers" => 0, - "passwordfile" => 0, - "bannedports" => '', - "maxportallowed" => 0, - "outputfile" => 0, - "noprocess" => 0, - "dbstat" => 0, - "nodbstat" => 0, - "server-log" => '', - "tbstat" => 0, - "notbstat" => 0, - "colstat" => 0, - "nocolstat" => 0, - "idxstat" => 0, - "noidxstat" => 0, - "nomyisamstat" => 0, - "nostructstat" => 0, - "sysstat" => 0, - "nosysstat" => 0, - "pfstat" => 0, - "nopfstat" => 0, - "skippassword" => 0, - "noask" => 0, - "template" => 0, - "json" => 0, - "prettyjson" => 0, - "reportfile" => 0, - "verbose" => 0, - "experimental" => 0, - "nondedicated" => 0, - "defaults-file" => '', - "defaults-extra-file" => '', - "protocol" => '', - "dumpdir" => '', - "feature" => '', - "dbgpattern" => '', - "defaultarch" => 64, - "noprettyicon" => 0 -); - -# Gather the options from the command line -GetOptions( - \%opt, 'nobad', - 'nogood', 'noinfo', - 'debug', 'nocolor', - 'forcemem=i', 'forceswap=i', - 'host=s', 'socket=s', - 'port=i', 'user=s', - 'pass=s', 'skipsize', - 'checkversion', 'mysqladmin=s', - 'mysqlcmd=s', 'help', - 'buffers', 'skippassword', - 'passwordfile=s', 'outputfile=s', - 'silent', 'noask', - 'json', 'prettyjson', - 'template=s', 'reportfile=s', - 'cvefile=s', 'bannedports=s', - 'updateversion', 'maxportallowed=s', - 'verbose', 'password=s', - 'passenv=s', 'userenv=s', - 'defaults-file=s', 'ssl-ca=s', - 'color', 'noprocess', - 'dbstat', 'nodbstat', - 'tbstat', 'notbstat', - 'colstat', 'nocolstat', - 'sysstat', 'nosysstat', - 'pfstat', 'nopfstat', - 'idxstat', 'noidxstat', - 'structstat', 'nostructstat', - 'myisamstat', 'nomyisamstat', - 'server-log=s', 'protocol=s', - 'defaults-extra-file=s', 'dumpdir=s', - 'feature=s', 'dbgpattern=s', - 'defaultarch=i', 'experimental', - 'nondedicated', 'noprettyicon' -) - or pod2usage( - -exitval => 1, - -verbose => 99, - -sections => [ - "NAME", - "IMPORTANT USAGE GUIDELINES", - "CONNECTION AND AUTHENTICATION", - "PERFORMANCE AND REPORTING OPTIONS", - "OUTPUT OPTIONS" - ] -); - -if ( defined $opt{'help'} && $opt{'help'} == 1 ) { - pod2usage( - -exitval => 0, - -verbose => 99, - -sections => [ - "NAME", - "IMPORTANT USAGE GUIDELINES", - "CONNECTION AND AUTHENTICATION", - "PERFORMANCE AND REPORTING OPTIONS", - "OUTPUT OPTIONS" - ] - ); -} - -my $devnull = File::Spec->devnull(); -my $basic_password_files = - ( $opt{passwordfile} eq "0" ) - ? abs_path( dirname(__FILE__) ) . "/basic_passwords.txt" - : abs_path( $opt{passwordfile} ); - -# Username from envvar -if ( exists $opt{userenv} && exists $ENV{ $opt{userenv} } ) { - $opt{user} = $ENV{ $opt{userenv} }; -} - -# Related to password option -if ( exists $opt{passenv} && exists $ENV{ $opt{passenv} } ) { - $opt{pass} = $ENV{ $opt{passenv} }; -} -$opt{pass} = $opt{password} if ( $opt{pass} eq 0 and $opt{password} ne 0 ); - -if ( $opt{dumpdir} ne '' ) { - $opt{dumpdir} = abs_path( $opt{dumpdir} ); - if ( !-d $opt{dumpdir} ) { - mkdir $opt{dumpdir} or die "Cannot create directory $opt{dumpdir}: $!"; - } -} - -# for RPM distributions -$basic_password_files = "/usr/share/mysqltuner/basic_passwords.txt" - unless -f "$basic_password_files"; - -$opt{dbgpattern} = '.*' if ( $opt{dbgpattern} eq '' ); - -# Activate debug variables -#if ( $opt{debug} ne '' ) { $opt{debug} = 2; } -# Activate experimental calculations and analysis -#if ( $opt{experimental} ne '' ) { $opt{experimental} = 1; } - -# check if we need to enable verbose mode -if ( $opt{feature} ne '' ) { $opt{verbose} = 1; } -if ( $opt{verbose} ) { - $opt{checkversion} = 0; # Check for updates to MySQLTuner - $opt{dbstat} = 1; # Print database information - $opt{tbstat} = 1; # Print database information - $opt{idxstat} = 1; # Print index information - $opt{sysstat} = 1; # Print index information - $opt{buffers} = 1; # Print global and per-thread buffer values - $opt{pfstat} = 1; # Print performance schema info. - $opt{structstat} = 1; # Print table structure information - $opt{myisamstat} = 1; # Print MyISAM table information - - $opt{cvefile} = 'vulnerabilities.csv'; #CVE File for vulnerability checks -} -$opt{noprettyicon}=0 if $opt{noprettyicon}!=1; -$opt{nocolor} = 1 if defined( $opt{outputfile} ); -$opt{tbstat} = 0 if ( $opt{notbstat} == 1 ); # Don't print table information -$opt{colstat} = 0 if ( $opt{nocolstat} == 1 ); # Don't print column information -$opt{dbstat} = 0 if ( $opt{nodbstat} == 1 ); # Don't print database information -$opt{noprocess} = 0 - if ( $opt{noprocess} == 1 ); # Don't print process information -$opt{sysstat} = 0 if ( $opt{nosysstat} == 1 ); # Don't print sysstat information -$opt{pfstat} = 0 - if ( $opt{nopfstat} == 1 ); # Don't print performance schema information -$opt{idxstat} = 0 if ( $opt{noidxstat} == 1 ); # Don't print index information -$opt{structstat} = 0 - if ( not defined( $opt{structstat} ) or $opt{nostructstat} == 1 ) -; # Don't print table struct information -$opt{myisamstat} = 1 - if ( not defined( $opt{myisamstat} ) ); -$opt{myisamstat} = 0 - if ( $opt{nomyisamstat} == 1 ); # Don't print MyISAM table information - -# for RPM distributions -$opt{cvefile} = "/usr/share/mysqltuner/vulnerabilities.csv" - unless ( defined $opt{cvefile} and -f "$opt{cvefile}" ); -$opt{cvefile} = '' unless -f "$opt{cvefile}"; -$opt{cvefile} = './vulnerabilities.csv' if -f './vulnerabilities.csv'; - -$opt{'bannedports'} = '' unless defined( $opt{'bannedports'} ); -my @banned_ports = split ',', $opt{'bannedports'}; - -# -my $outputfile = undef; -$outputfile = abs_path( $opt{outputfile} ) unless $opt{outputfile} eq "0"; - -my $fh = undef; -open( $fh, '>', $outputfile ) - or die("Fail opening $outputfile") - if defined($outputfile); -$opt{nocolor} = 1 if defined($outputfile); -$opt{nocolor} = 1 unless ( -t STDOUT ); - -$opt{nocolor} = 0 if ( $opt{color} == 1 ); - -# Setting up the colors for the print styles -my $me = `whoami`; -$me =~ s/\n//g; - - -my $good = ( $opt{nocolor} == 0 ) ? "[\e[0;32mOK\e[0m]" : "[OK]"; -my $bad = ( $opt{nocolor} == 0 ) ? "[\e[0;31m!!\e[0m]" : "[!!]"; -my $info = ( $opt{nocolor} == 0 ) ? "[\e[0;34m--\e[0m]" : "[--]"; -my $deb = ( $opt{nocolor} == 0 ) ? "[\e[0;31mDG\e[0m]" : "[DG]"; -my $cmd = ( $opt{nocolor} == 0 ) ? "\e[1;32m[CMD]($me)" : "[CMD]($me)"; -my $end = ( $opt{nocolor} == 0 ) ? "\e[0m" : ""; - -if ($opt{noprettyicon} == 0) { - $good = "✔ "; - $bad = "✘ "; - $info = "ℹ "; - $deb = "⚙ "; - $cmd = "⌨️($me)"; - $end = " "; -} - -# Maximum lines of log output to read from end -my $maxlines = 30000; - -# Checks for supported or EOL'ed MySQL versions -my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ); - -# Database -my @dblist; - -# Super structure containing all information -my %result; -$result{'MySQLTuner'}{'version'} = $tunerversion; -$result{'MySQLTuner'}{'datetime'} = `date '+%d-%m-%Y %H:%M:%S'`; -$result{'MySQLTuner'}{'options'} = \%opt; - -# Functions that handle the print styles -sub prettyprint { - print $_[0] . "\n" unless ( $opt{'silent'} or $opt{'json'} ); - print $fh $_[0] . "\n" if defined($fh); -} - -sub goodprint { - prettyprint $good. " " . $_[0] unless ( $opt{nogood} == 1 ); -} - -sub infoprint { - prettyprint $info. " " . $_[0] unless ( $opt{noinfo} == 1 ); -} - -sub badprint { - prettyprint $bad. " " . $_[0] unless ( $opt{nobad} == 1 ); -} - -sub debugprint { - prettyprint $deb. " " . $_[0] unless ( $opt{debug} == 0 ); -} - -sub redwrap { - return ( $opt{nocolor} == 0 ) ? "\e[0;31m" . $_[0] . "\e[0m" : $_[0]; -} - -sub greenwrap { - return ( $opt{nocolor} == 0 ) ? "\e[0;32m" . $_[0] . "\e[0m" : $_[0]; -} - -sub cmdprint { - prettyprint $cmd. " " . $_[0] . $end; -} - -sub infoprintml { - for my $ln (@_) { $ln =~ s/\n//g; infoprint "\t$ln"; } -} - -sub infoprintcmd { - cmdprint "@_"; - infoprintml grep { $_ ne '' and $_ !~ /^\s*$/ } `@_ 2>&1`; -} - -sub subheaderprint { - my $tln = 100; - my $sln = 8; - my $ln = length("@_") + 2; - - prettyprint " "; - prettyprint "-" x $sln . " @_ " . "-" x ( $tln - $ln - $sln ); -} - -sub infoprinthcmd { - subheaderprint "$_[0]"; - infoprintcmd "$_[1]"; -} - -sub is_remote() { - my $host = $opt{'host'}; - return 0 if ( $host eq '' ); - return 0 if ( $host eq 'localhost' ); - return 0 if ( $host eq '127.0.0.1' ); - return 1; -} - -sub is_int { - return 0 unless defined $_[0]; - my $str = $_[0]; - - #trim whitespace both sides - $str =~ s/^\s+|\s+$//g; - - #Alternatively, to match any float-like numeric, use: - # m/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ - - #flatten to string and match dash or plus and one or more digits - if ( $str =~ /^(\-|\+)?\d+?$/ ) { - return 1; - } - return 0; -} - -# Calculates the number of physical cores considering HyperThreading -sub cpu_cores { - if ( $^O eq 'linux' ) { - my $cntCPU = - `awk -F: '/^core id/ && !P[\$2] { CORES++; P[\$2]=1 }; /^physical id/ && !N[\$2] { CPUs++; N[\$2]=1 }; END { print CPUs*CORES }' /proc/cpuinfo`; - chomp $cntCPU; - return ( $cntCPU == 0 ? `nproc` : $cntCPU ); - } - - if ( $^O eq 'freebsd' ) { - my $cntCPU = `sysctl -n kern.smp.cores`; - chomp $cntCPU; - return $cntCPU + 0; - } - return 0; -} - -# Calculates the parameter passed in bytes, then rounds it to one decimal place -sub hr_bytes { - my $num = shift; - return "0B" unless defined($num); - return "0B" if $num eq "NULL"; - return "0B" if $num eq ""; - - if ( $num >= ( 1024**3 ) ) { # GB - return sprintf( "%.1f", ( $num / ( 1024**3 ) ) ) . "G"; - } - elsif ( $num >= ( 1024**2 ) ) { # MB - return sprintf( "%.1f", ( $num / ( 1024**2 ) ) ) . "M"; - } - elsif ( $num >= 1024 ) { # KB - return sprintf( "%.1f", ( $num / 1024 ) ) . "K"; - } - else { - return $num . "B"; - } -} - -sub hr_raw { - my $num = shift; - return "0" unless defined($num); - return "0" if $num eq "NULL"; - if ( $num =~ /^(\d+)G$/ ) { - return $1 * 1024 * 1024 * 1024; - } - if ( $num =~ /^(\d+)M$/ ) { - return $1 * 1024 * 1024; - } - if ( $num =~ /^(\d+)K$/ ) { - return $1 * 1024; - } - if ( $num =~ /^(\d+)$/ ) { - return $1; - } - return $num; -} - -# Calculates the parameter passed in bytes, then rounds it to the nearest integer -sub hr_bytes_rnd { - my $num = shift; - return "0B" unless defined($num); - return "0B" if $num eq "NULL"; - - if ( $num >= ( 1024**3 ) ) { # GB - return int( ( $num / ( 1024**3 ) ) ) . "G"; - } - elsif ( $num >= ( 1024**2 ) ) { # MB - return int( ( $num / ( 1024**2 ) ) ) . "M"; - } - elsif ( $num >= 1024 ) { # KB - return int( ( $num / 1024 ) ) . "K"; - } - else { - return $num . "B"; - } -} - -# Calculates the parameter passed to the nearest power of 1000, then rounds it to the nearest integer -sub hr_num { - my $num = shift; - if ( $num >= ( 1000**3 ) ) { # Billions - return int( ( $num / ( 1000**3 ) ) ) . "B"; - } - elsif ( $num >= ( 1000**2 ) ) { # Millions - return int( ( $num / ( 1000**2 ) ) ) . "M"; - } - elsif ( $num >= 1000 ) { # Thousands - return int( ( $num / 1000 ) ) . "K"; - } - else { - return $num; - } -} - -# Calculate Percentage -sub percentage { - my $value = shift; - my $total = shift; - $total = 0 unless defined $total; - $total = 0 if $total eq "NULL"; - return 100, 00 if $total == 0; - return sprintf( "%.2f", ( $value * 100 / $total ) ); -} - -# Calculates uptime to display in a human-readable form -sub pretty_uptime { - my $uptime = shift; - my $seconds = $uptime % 60; - my $minutes = int( ( $uptime % 3600 ) / 60 ); - my $hours = int( ( $uptime % 86400 ) / (3600) ); - my $days = int( $uptime / (86400) ); - my $uptimestring; - if ( $days > 0 ) { - $uptimestring = "${days}d ${hours}h ${minutes}m ${seconds}s"; - } - elsif ( $hours > 0 ) { - $uptimestring = "${hours}h ${minutes}m ${seconds}s"; - } - elsif ( $minutes > 0 ) { - $uptimestring = "${minutes}m ${seconds}s"; - } - else { - $uptimestring = "${seconds}s"; - } - return $uptimestring; -} - -# Retrieves the memory installed on this machine -my ( $physical_memory, $swap_memory, $duflags, $xargsflags ); - -sub memerror { - badprint - "Unable to determine total memory/swap; use '--forcemem' and '--forceswap'"; - exit 1; -} - -sub os_setup { - my $os = `uname`; - $duflags = ( $os =~ /Linux/ ) ? '-b' : ''; - $xargsflags = ( $os =~ /Darwin|SunOS/ ) ? '' : '-r'; - if ( $opt{'forcemem'} > 0 ) { - $physical_memory = $opt{'forcemem'} * 1048576; - infoprint "Assuming $opt{'forcemem'} MB of physical memory"; - if ( $opt{'forceswap'} > 0 ) { - $swap_memory = $opt{'forceswap'} * 1048576; - infoprint "Assuming $opt{'forceswap'} MB of swap space"; - } - else { - $swap_memory = 0; - badprint "Assuming 0 MB of swap space (use --forceswap to specify)"; - } - } - else { - if ( $os =~ /Linux|CYGWIN/ ) { - $physical_memory = - `grep -i memtotal: /proc/meminfo | awk '{print \$2}'` - or memerror; - $physical_memory *= 1024; - - $swap_memory = - `grep -i swaptotal: /proc/meminfo | awk '{print \$2}'` - or memerror; - $swap_memory *= 1024; - } - elsif ( $os =~ /Darwin/ ) { - $physical_memory = `sysctl -n hw.memsize` or memerror; - $swap_memory = - `sysctl -n vm.swapusage | awk '{print \$3}' | sed 's/\..*\$//'` - or memerror; - } - elsif ( $os =~ /NetBSD|OpenBSD|FreeBSD/ ) { - $physical_memory = `sysctl -n hw.physmem` or memerror; - if ( $physical_memory < 0 ) { - $physical_memory = `sysctl -n hw.physmem64` or memerror; - } - $swap_memory = - `swapctl -l | grep '^/' | awk '{ s+= \$2 } END { print s }'` - or memerror; - } - elsif ( $os =~ /BSD/ ) { - $physical_memory = `sysctl -n hw.realmem` or memerror; - $swap_memory = - `swapinfo | grep '^/' | awk '{ s+= \$2 } END { print s }'`; - } - elsif ( $os =~ /SunOS/ ) { - $physical_memory = - `/usr/sbin/prtconf | grep Memory | cut -f 3 -d ' '` - or memerror; - chomp($physical_memory); - $physical_memory = $physical_memory * 1024 * 1024; - } - elsif ( $os =~ /AIX/ ) { - $physical_memory = - `lsattr -El sys0 | grep realmem | awk '{print \$2}'` - or memerror; - chomp($physical_memory); - $physical_memory = $physical_memory * 1024; - $swap_memory = `lsps -as | awk -F"(MB| +)" '/MB /{print \$2}'` - or memerror; - chomp($swap_memory); - $swap_memory = $swap_memory * 1024 * 1024; - } - elsif ( $os =~ /windows/i ) { - $physical_memory = - `wmic ComputerSystem get TotalPhysicalMemory | perl -ne "chomp; print if /[0-9]+/;"` - or memerror; - $swap_memory = - `wmic OS get FreeVirtualMemory | perl -ne "chomp; print if /[0-9]+/;"` - or memerror; - } - } - debugprint "Physical Memory: $physical_memory"; - debugprint "Swap Memory: $swap_memory"; - chomp($physical_memory); - chomp($swap_memory); - chomp($os); - $result{'OS'}{'OS Type'} = $os; - $result{'OS'}{'Physical Memory'}{'bytes'} = $physical_memory; - $result{'OS'}{'Physical Memory'}{'pretty'} = hr_bytes($physical_memory); - $result{'OS'}{'Swap Memory'}{'bytes'} = $swap_memory; - $result{'OS'}{'Swap Memory'}{'pretty'} = hr_bytes($swap_memory); - $result{'OS'}{'Other Processes'}{'bytes'} = get_other_process_memory(); - $result{'OS'}{'Other Processes'}{'pretty'} = - hr_bytes( get_other_process_memory() ); -} - -sub get_http_cli { - my $httpcli = which( "curl", $ENV{'PATH'} ); - chomp($httpcli); - if ($httpcli) { - return $httpcli; - } - - $httpcli = which( "wget", $ENV{'PATH'} ); - chomp($httpcli); - if ($httpcli) { - return $httpcli; - } - return ""; -} - -# Checks for updates to MySQLTuner -sub validate_tuner_version { - if ( $opt{'checkversion'} eq 0 ) { - print "\n" unless ( $opt{'silent'} or $opt{'json'} ); - infoprint "Skipped version check for MySQLTuner script"; - return; - } - - my $update; - my $url = - "https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl"; - my $httpcli = get_http_cli(); - if ( $httpcli =~ /curl$/ ) { - debugprint "$httpcli is available."; - - debugprint - "$httpcli -m 3 -silent '$url' 2>/dev/null | grep 'my \$tunerversion'| cut -d\\\" -f2"; - $update = - `$httpcli -m 3 -silent '$url' 2>/dev/null | grep 'my \$tunerversion'| cut -d\\\" -f2`; - chomp($update); - debugprint "VERSION: $update"; - - compare_tuner_version($update); - return; - } - - if ( $httpcli =~ /wget$/ ) { - debugprint "$httpcli is available."; - - debugprint - "$httpcli -e timestamping=off -t 1 -T 3 -O - '$url' 2>$devnull| grep 'my \$tunerversion'| cut -d\\\" -f2"; - $update = - `$httpcli -e timestamping=off -t 1 -T 3 -O - '$url' 2>$devnull| grep 'my \$tunerversion'| cut -d\\\" -f2`; - chomp($update); - compare_tuner_version($update); - return; - } - debugprint "curl and wget are not available."; - infoprint "Unable to check for the latest MySQLTuner version"; - infoprint - "Using --pass and --password option is insecure during MySQLTuner execution (password disclosure)" - if ( defined( $opt{'pass'} ) ); -} - -# Checks for updates to MySQLTuner -sub update_tuner_version { - if ( $opt{'updateversion'} eq 0 ) { - badprint "Skipped version update for MySQLTuner script"; - print "\n" unless ( $opt{'silent'} or $opt{'json'} ); - return; - } - - my $update; - my $fullpath = ""; - my $url = "https://raw.githubusercontent.com/major/MySQLTuner-perl/master/"; - my @scripts = - ( "mysqltuner.pl", "basic_passwords.txt", "vulnerabilities.csv" ); - my $totalScripts = scalar(@scripts); - my $receivedScripts = 0; - my $httpcli = get_http_cli(); - - foreach my $script (@scripts) { - - if ( $httpcli =~ /curl$/ ) { - debugprint "$httpcli is available."; - - $fullpath = dirname(__FILE__) . "/" . $script; - debugprint "FullPath: $fullpath"; - debugprint - "$httpcli --connect-timeout 3 '$url$script' 2>$devnull > $fullpath"; - $update = - `$httpcli --connect-timeout 3 '$url$script' 2>$devnull > $fullpath`; - chomp($update); - debugprint "$script updated: $update"; - - if ( -s $script eq 0 ) { - badprint "Couldn't update $script"; - } - else { - ++$receivedScripts; - debugprint "$script updated: $update"; - } - } - elsif ( $httpcli =~ /wget$/ ) { - - debugprint "$httpcli is available."; - - debugprint - "$httpcli -qe timestamping=off -t 1 -T 3 -O $script '$url$script'"; - $update = - `$httpcli -qe timestamping=off -t 1 -T 3 -O $script '$url$script'`; - chomp($update); - - if ( -s $script eq 0 ) { - badprint "Couldn't update $script"; - } - else { - ++$receivedScripts; - debugprint "$script updated: $update"; - } - } - else { - debugprint "curl and wget are not available."; - infoprint "Unable to check for the latest MySQLTuner version"; - } - - } - - if ( $receivedScripts eq $totalScripts ) { - goodprint "Successfully updated MySQLTuner script"; - } - else { - badprint "Couldn't update MySQLTuner script"; - } - infoprint "Stopping program: MySQLTuner script must be updated first."; - exit 0; -} - -sub compare_tuner_version { - my $remoteversion = shift; - debugprint "Remote data: $remoteversion"; - - #exit 0; - if ( $remoteversion ne $tunerversion ) { - badprint - "There is a new version of MySQLTuner available ($remoteversion)"; - update_tuner_version(); - return; - } - goodprint "You have the latest version of MySQLTuner ($tunerversion)"; - return; -} - -# Checks to see if a MySQL login is possible -my ( $mysqllogin, $doremote, $remotestring, $mysqlcmd, $mysqladmincmd ); - -my $osname = $^O; -if ( $osname eq 'MSWin32' ) { - eval { require Win32; } or last; - $osname = Win32::GetOSName(); - infoprint "* Windows OS ($osname) is not fully supported.\n"; - - #exit 1; -} - -sub mysql_setup { - $doremote = 0; - $remotestring = ''; - if ( $opt{mysqladmin} ) { - $mysqladmincmd = $opt{mysqladmin}; - } - else { - $mysqladmincmd = which( "mariadb-admin", $ENV{'PATH'} ); - if ( !-e $mysqladmincmd ) { - $mysqladmincmd = which( "mysqladmin", $ENV{'PATH'} ); - } - } - chomp($mysqladmincmd); - if ( !-e $mysqladmincmd && $opt{mysqladmin} ) { - badprint "Unable to find the mysqladmin command you specified: " - . $mysqladmincmd . ""; - exit 1; - } - elsif ( !-e $mysqladmincmd ) { - badprint - "Couldn't find mysqladmin/mariadb-admin in your \$PATH. Is MySQL installed?"; - - #exit 1; - } - if ( $opt{mysqlcmd} ) { - $mysqlcmd = $opt{mysqlcmd}; - } - else { - $mysqlcmd = which( "mariadb", $ENV{'PATH'} ); - if ( !-e $mysqlcmd ) { - $mysqlcmd = which( "mysql", $ENV{'PATH'} ); - } - } - chomp($mysqlcmd); - if ( !-e $mysqlcmd && $opt{mysqlcmd} ) { - badprint "Unable to find the mysql command you specified: " - . $mysqlcmd . ""; - exit 1; - } - elsif ( !-e $mysqlcmd ) { - badprint - "Couldn't find mysql/mariadb in your \$PATH. Is MySQL installed?"; - exit 1; - } - $mysqlcmd =~ s/\n$//g; - my $mysqlclidefaults = `$mysqlcmd --print-defaults`; - debugprint "MySQL Client: $mysqlclidefaults"; - if ( $mysqlclidefaults =~ /auto-vertical-output/ ) { - badprint - "Avoid auto-vertical-output in configuration file(s) for MySQL like"; - exit 1; - } - - debugprint "MySQL Client: $mysqlcmd"; - - # Are we being asked to connect via a socket? - if ( $opt{socket} ne 0 ) { - if ( $opt{port} ne 0 ) { - $remotestring = " -S $opt{socket} -P $opt{port}"; - } - else { - $remotestring = " -S $opt{socket}"; - } - } - - if ( $opt{protocol} ne '' ) { - $remotestring = " --protocol=$opt{protocol}"; - } - - # Are we being asked to connect to a remote server? - if ( $opt{host} ne 0 ) { - chomp( $opt{host} ); - $opt{port} = ( $opt{port} eq 0 ) ? 3306 : $opt{port}; - - # If we're doing a remote connection, but forcemem wasn't specified, we need to exit - if ( $opt{'forcemem'} eq 0 && is_remote eq 1 ) { - badprint "The --forcemem option is required for remote connections"; - badprint - "Assuming RAM memory is 1Gb for simplify remote connection usage"; - $opt{'forcemem'} = 1024; - - #exit 1; - } - if ( $opt{'forceswap'} eq 0 && is_remote eq 1 ) { - badprint - "The --forceswap option is required for remote connections"; - badprint - "Assuming Swap size is 1Gb for simplify remote connection usage"; - $opt{'forceswap'} = 1024; - - #exit 1; - } - infoprint "Performing tests on $opt{host}:$opt{port}"; - $remotestring = " -h $opt{host} -P $opt{port}"; - $doremote = is_remote(); - - } - else { - $opt{host} = '127.0.0.1'; - } - - if ( $opt{'ssl-ca'} ne 0 ) { - if ( -e -r -f $opt{'ssl-ca'} ) { - $remotestring .= " --ssl-ca=$opt{'ssl-ca'}"; - infoprint - "Will connect using ssl public key passed on the command line"; - return 1; - } - else { - badprint - "Attempted to use passed ssl public key, but it was not found or could not be read"; - exit 1; - } - } - - # Did we already get a username with or without password on the command line? - if ( $opt{user} ne 0 ) { - $mysqllogin = - "-u $opt{user} " - . ( ( $opt{pass} ne 0 ) ? "-p'$opt{pass}' " : " " ) - . $remotestring; - my $loginstatus = - `$mysqlcmd -Nrs -e 'select "mysqld is alive";' $mysqllogin 2>&1`; - if ( $loginstatus =~ /mysqld is alive/ ) { - goodprint "Logged in using credentials passed on the command line"; - return 1; - } - else { - badprint - "Attempted to use login credentials, but they were invalid"; - exit 1; - } - } - - my $svcprop = which( "svcprop", $ENV{'PATH'} ); - if ( substr( $svcprop, 0, 1 ) =~ "/" ) { - - # We are on solaris - ( my $mysql_login = - `svcprop -p quickbackup/username svc:/network/mysql-quickbackup:default` - ) =~ s/\s+$//; - ( my $mysql_pass = - `svcprop -p quickbackup/password svc:/network/mysql-quickbackup:default` - ) =~ s/\s+$//; - if ( substr( $mysql_login, 0, 7 ) ne "svcprop" ) { - - # mysql-quickbackup is installed - $mysqllogin = "-u $mysql_login -p$mysql_pass"; - my $loginstatus = `mysqladmin $mysqllogin ping 2>&1`; - if ( $loginstatus =~ /mysqld is alive/ ) { - goodprint "Logged in using credentials from mysql-quickbackup."; - return 1; - } - else { - badprint - "Attempted to use login credentials from mysql-quickbackup, but they failed."; - exit 1; - } - } - } - elsif ( -r "/etc/psa/.psa.shadow" and $doremote == 0 ) { - - # It's a Plesk box, use the available credentials - $mysqllogin = "-u admin -p`cat /etc/psa/.psa.shadow`"; - my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; - unless ( $loginstatus =~ /mysqld is alive/ ) { - - # Plesk 10+ - $mysqllogin = - "-u admin -p`/usr/local/psa/bin/admin --show-password`"; - $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; - unless ( $loginstatus =~ /mysqld is alive/ ) { - badprint - "Attempted to use login credentials from Plesk and Plesk 10+, but they failed."; - exit 1; - } - } - } - elsif ( -r "/usr/local/directadmin/conf/mysql.conf" and $doremote == 0 ) { - - # It's a DirectAdmin box, use the available credentials - my $mysqluser = - `cat /usr/local/directadmin/conf/mysql.conf | egrep '^user=.*'`; - my $mysqlpass = - `cat /usr/local/directadmin/conf/mysql.conf | egrep '^passwd=.*'`; - - $mysqluser =~ s/user=//; - $mysqluser =~ s/[\r\n]//; - $mysqlpass =~ s/passwd=//; - $mysqlpass =~ s/[\r\n]//; - - $mysqllogin = "-u $mysqluser -p$mysqlpass"; - - my $loginstatus = `mysqladmin ping $mysqllogin 2>&1`; - unless ( $loginstatus =~ /mysqld is alive/ ) { - badprint - "Attempted to use login credentials from DirectAdmin, but they failed."; - exit 1; - } - } - elsif ( -r "/etc/mysql/debian.cnf" - and $doremote == 0 - and $opt{'defaults-file'} eq '' ) - { - - # We have a Debian maintenance account, use it - $mysqllogin = "--defaults-file=/etc/mysql/debian.cnf"; - my $loginstatus = `$mysqladmincmd $mysqllogin ping 2>&1`; - if ( $loginstatus =~ /mysqld is alive/ ) { - goodprint - "Logged in using credentials from Debian maintenance account."; - return 1; - } - else { - badprint - "Attempted to use login credentials from Debian maintenance account, but they failed."; - exit 1; - } - } - elsif ( $opt{'defaults-file'} ne '' and -r "$opt{'defaults-file'}" ) { - - # defaults-file - debugprint "defaults file detected: $opt{'defaults-file'}"; - my $mysqlclidefaults = `$mysqlcmd --print-defaults`; - debugprint "MySQL Client Default File: $opt{'defaults-file'}"; - - $mysqllogin = "--defaults-file=" . $opt{'defaults-file'}; - my $loginstatus = `$mysqladmincmd $mysqllogin ping 2>&1`; - if ( $loginstatus =~ /mysqld is alive/ ) { - goodprint "Logged in using credentials from defaults file account."; - return 1; - } - } - elsif ( $opt{'defaults-extra-file'} ne '' - and -r "$opt{'defaults-extra-file'}" ) - { - - # defaults-extra-file - debugprint "defaults extra file detected: $opt{'defaults-extra-file'}"; - my $mysqlclidefaults = `$mysqlcmd --print-defaults`; - debugprint - "MySQL Client Extra Default File: $opt{'defaults-extra-file'}"; - - $mysqllogin = "--defaults-extra-file=" . $opt{'defaults-extra-file'}; - my $loginstatus = `$mysqladmincmd $mysqllogin ping 2>&1`; - if ( $loginstatus =~ /mysqld is alive/ ) { - goodprint - "Logged in using credentials from extra defaults file account."; - return 1; - } - } - else { - # It's not Plesk or Debian, we should try a login - debugprint "$mysqladmincmd $remotestring ping 2>&1"; - - #my $loginstatus = ""; - debugprint "Using mysqlcmd: $mysqlcmd"; - - #if (defined($mysqladmincmd)) { - # infoprint "Using mysqladmin to check login"; - # $loginstatus=`$mysqladmincmd $remotestring ping 2>&1`; - #} else { - infoprint "Using mysql to check login"; - my $loginstatus = - `$mysqlcmd $remotestring -Nrs -e 'select "mysqld is alive"' --connect-timeout=3 2>&1`; - - #} - - if ( $loginstatus =~ /mysqld is alive/ ) { - - # Login went just fine - $mysqllogin = " $remotestring "; - - # Did this go well because of a .my.cnf file or is there no password set? - my $userpath = `printenv HOME`; - if ( length($userpath) > 0 ) { - chomp($userpath); - } - unless ( -e "${userpath}/.my.cnf" or -e "${userpath}/.mylogin.cnf" ) - { - badprint - "SECURITY RISK: Successfully authenticated without password"; - } - return 1; - } - else { - if ( $opt{'noask'} == 1 ) { - badprint - "Attempted to use login credentials, but they were invalid"; - exit 1; - } - my ( $name, $password ); - - # If --user is defined no need to ask for username - if ( $opt{user} ne 0 ) { - $name = $opt{user}; - } - else { - print STDERR "Please enter your MySQL administrative login: "; - $name = ; - } - - # If --pass is defined no need to ask for password - if ( $opt{pass} ne 0 ) { - $password = $opt{pass}; - } - else { - print STDERR - "Please enter your MySQL administrative password: "; - system("stty -echo >$devnull 2>&1"); - $password = ; - system("stty echo >$devnull 2>&1"); - } - chomp($password); - chomp($name); - $mysqllogin = "-u $name"; - - if ( length($password) > 0 ) { - $mysqllogin .= " -p'$password'"; - } - $mysqllogin .= $remotestring; - my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; - if ( $loginstatus =~ /mysqld is alive/ ) { - - #print STDERR ""; - if ( !length($password) ) { - - # Did this go well because of a .my.cnf file or is there no password set? - my $userpath = `printenv HOME`; - chomp($userpath); - unless ( -e "$userpath/.my.cnf" ) { - print STDERR ""; - badprint - "SECURITY RISK: Successfully authenticated without password"; - } - } - return 1; - } - else { - #print STDERR ""; - badprint - "Attempted to use login credentials, but they were invalid."; - exit 1; - } - exit 1; - } - } -} - -# MySQL Request Array -sub select_array { - my $req = shift; - debugprint "PERFORM: $req "; - my @result = `$mysqlcmd $mysqllogin -Bse "\\w$req" 2>>/dev/null`; - if ( $? != 0 ) { - badprint "Failed to execute: $req"; - badprint "FAIL Execute SQL / return code: $?"; - debugprint "CMD : $mysqlcmd"; - debugprint "OPTIONS: $mysqllogin"; - debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`; - - #exit $?; - } - debugprint "select_array: return code : $?"; - chomp(@result); - return @result; -} - -# MySQL Request Array -sub select_array_with_headers { - my $req = shift; - debugprint "PERFORM: $req "; - my @result = `$mysqlcmd $mysqllogin -Bre "\\w$req" 2>>/dev/null`; - if ( $? != 0 ) { - badprint "Failed to execute: $req"; - badprint "FAIL Execute SQL / return code: $?"; - debugprint "CMD : $mysqlcmd"; - debugprint "OPTIONS: $mysqllogin"; - debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`; - - #exit $?; - } - debugprint "select_array_with_headers: return code : $?"; - chomp(@result); - return @result; -} - -# MySQL Request Array -sub select_csv_file { - my $tfile = shift; - my $req = shift; - debugprint "PERFORM: $req CSV into $tfile"; - - #return; - my @result = select_array_with_headers($req); - open( my $fh, '>', $tfile ) or die "Could not open file '$tfile' $!"; - for my $l (@result) { - $l =~ s/\t/","/g; - $l =~ s/^/"/; - $l =~ s/$/"\n/; - print $fh $l; - print $l if $opt{debug}; - } - close $fh; - infoprint "CSV file $tfile created"; -} - -sub human_size { - my ( $size, $n ) = ( shift, 0 ); - ++$n and $size /= 1024 until $size < 1024; - return sprintf "%.2f %s", $size, (qw[ bytes KB MB GB TB ])[$n]; -} - -# MySQL Request one -sub select_one { - my $req = shift; - debugprint "PERFORM: $req "; - my $result = `$mysqlcmd $mysqllogin -Bse "\\w$req" 2>>/dev/null`; - if ( $? != 0 ) { - badprint "Failed to execute: $req"; - badprint "FAIL Execute SQL / return code: $?"; - debugprint "CMD : $mysqlcmd"; - debugprint "OPTIONS: $mysqllogin"; - debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`; - - #exit $?; - } - debugprint "select_array: return code : $?"; - chomp($result); - return $result; -} - -# MySQL Request one -sub select_one_g { - my $pattern = shift; - - my $req = shift; - debugprint "PERFORM: $req "; - my @result = `$mysqlcmd $mysqllogin -re "\\w$req\\G" 2>>/dev/null`; - if ( $? != 0 ) { - badprint "Failed to execute: $req"; - badprint "FAIL Execute SQL / return code: $?"; - debugprint "CMD : $mysqlcmd"; - debugprint "OPTIONS: $mysqllogin"; - debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`; - - #exit $?; - } - debugprint "select_array: return code : $?"; - chomp(@result); - return ( grep { /$pattern/ } @result )[0]; -} - -sub select_str_g { - my $pattern = shift; - - my $req = shift; - my $str = select_one_g $pattern, $req; - return () unless defined $str; - my @val = split /:/, $str; - shift @val; - return trim(@val); -} - -sub select_user_dbs { - return select_array( - "SELECT DISTINCT TABLE_SCHEMA FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'percona', 'sys')" - ); -} - -sub select_tables_db { - my $schema = shift; - return select_array( - "SELECT DISTINCT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$schema'" - ); -} - -sub select_indexes_db { - my $schema = shift; - return select_array( - "SELECT DISTINCT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='$schema'" - ); -} - -sub select_views_db { - my $schema = shift; - return select_array( - "SELECT DISTINCT TABLE_NAME FROM information_schema.VIEWS WHERE TABLE_SCHEMA='$schema'" - ); -} - -sub select_triggers_db { - my $schema = shift; - return select_array( - "SELECT DISTINCT TRIGGER_NAME FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA='$schema'" - ); -} - -sub select_routines_db { - my $schema = shift; - return select_array( - "SELECT DISTINCT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA='$schema'" - ); -} - -sub select_table_indexes_db { - my $schema = shift; - my $tbname = shift; - return select_array( - "SELECT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='$schema' AND TABLE_NAME='$tbname'" - ); -} - -sub select_table_columns_db { - my $schema = shift; - my $table = shift; - return select_array( - "SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$schema' AND TABLE_NAME='$table'" - ); -} - -sub get_tuning_info { - my @infoconn = select_array "\\s"; - my ( $tkey, $tval ); - @infoconn = - grep { !/Threads:/ and !/Connection id:/ and !/pager:/ and !/Using/ } - @infoconn; - foreach my $line (@infoconn) { - if ( $line =~ /\s*(.*):\s*(.*)/ ) { - debugprint "$1 => $2"; - $tkey = $1; - $tval = $2; - chomp($tkey); - chomp($tval); - $result{'MySQL Client'}{$tkey} = $tval; - } - } - $result{'MySQL Client'}{'Client Path'} = $mysqlcmd; - $result{'MySQL Client'}{'Admin Path'} = $mysqladmincmd; - $result{'MySQL Client'}{'Authentication Info'} = $mysqllogin; - -} - -# Populates all of the variable and status hashes -my ( %mystat, %myvar, $dummyselect, %myrepl, %myslaves ); - -sub arr2hash { - my $href = shift; - my $harr = shift; - my $sep = shift; - my $key = ''; - my $val = ''; - - $sep = '\s' unless defined($sep); - foreach my $line (@$harr) { - next if ( $line =~ m/^\*\*\*\*\*\*\*/ ); - $line =~ /([a-zA-Z_]*)\s*$sep\s*(.*)/; - $key = $1; - $val = $2; - $$href{$key} = $val; - - debugprint " * $key = $val" if $key =~ /$opt{dbgpattern}/i; - } -} - -sub get_all_vars { - - # We need to initiate at least one query so that our data is useable - $dummyselect = select_one "SELECT VERSION()"; - if ( not defined($dummyselect) or $dummyselect eq "" ) { - badprint - "You probably do not have enough privileges to run MySQLTuner ..."; - exit(256); - } - $dummyselect =~ s/(.*?)\-.*/$1/; - debugprint "VERSION: " . $dummyselect . ""; - $result{'MySQL Client'}{'Version'} = $dummyselect; - - my @mysqlvarlist = select_array("SHOW VARIABLES"); - push( @mysqlvarlist, select_array("SHOW GLOBAL VARIABLES") ); - arr2hash( \%myvar, \@mysqlvarlist ); - $result{'Variables'} = \%myvar; - - my @mysqlstatlist = select_array("SHOW STATUS"); - push( @mysqlstatlist, select_array("SHOW GLOBAL STATUS") ); - arr2hash( \%mystat, \@mysqlstatlist ); - $result{'Status'} = \%mystat; - unless ( defined( $myvar{'innodb_support_xa'} ) ) { - $myvar{'innodb_support_xa'} = 'ON'; - } - $mystat{'Uptime'} = 1 - unless defined( $mystat{'Uptime'} ) - and $mystat{'Uptime'} > 0; - $myvar{'have_galera'} = "NO"; - if ( defined( $myvar{'wsrep_provider_options'} ) - && $myvar{'wsrep_provider_options'} ne "" - && $myvar{'wsrep_on'} ne "OFF" ) - { - $myvar{'have_galera'} = "YES"; - debugprint "Galera options: " . $myvar{'wsrep_provider_options'}; - } - - # Workaround for MySQL bug #59393 wrt. ignore-builtin-innodb - if ( ( $myvar{'ignore_builtin_innodb'} || "" ) eq "ON" ) { - $myvar{'have_innodb'} = "NO"; - } - - # Support GTID MODE FOR MARIADB - # Issue MariaDB GTID mode #513 - $myvar{'gtid_mode'} = 'ON' - if ( defined( $myvar{'gtid_current_pos'} ) - and $myvar{'gtid_current_pos'} ne '' ); - - # Whether the server uses a thread pool to handle client connections - # MariaDB: thread_handling = pool-of-threads - # MySQL: thread_handling = loaded-dynamically - $myvar{'have_threadpool'} = "NO"; - if ( - defined( $myvar{'thread_handling'} ) - and ( $myvar{'thread_handling'} eq 'pool-of-threads' - || $myvar{'thread_handling'} eq 'loaded-dynamically' ) - ) - { - $myvar{'have_threadpool'} = "YES"; - } - - # have_* for engines is deprecated and will be removed in MySQL 5.6; - # check SHOW ENGINES and set corresponding old style variables. - # Also works around MySQL bug #59393 wrt. skip-innodb - my @mysqlenginelist = select_array "SHOW ENGINES"; - foreach my $line (@mysqlenginelist) { - if ( $line =~ /^([a-zA-Z_]+)\s+(\S+)/ ) { - my $engine = lc($1); - - if ( $engine eq "federated" || $engine eq "blackhole" ) { - $engine .= "_engine"; - } - elsif ( $engine eq "berkeleydb" ) { - $engine = "bdb"; - } - my $val = ( $2 eq "DEFAULT" ) ? "YES" : $2; - $myvar{"have_$engine"} = $val; - $result{'Storage Engines'}{$engine} = $2; - } - } - - #debugprint Dumper(@mysqlenginelist); - - my @mysqlslave; - if ( mysql_version_eq(8) or mysql_version_ge( 10, 5 ) ) { - @mysqlslave = select_array("SHOW REPLICA STATUS\\G"); - } - else { - @mysqlslave = select_array("SHOW SLAVE STATUS\\G"); - } - arr2hash( \%myrepl, \@mysqlslave, ':' ); - $result{'Replication'}{'Status'} = \%myrepl; - - my @mysqlslaves; - if ( mysql_version_eq(8) or mysql_version_ge( 10, 5 ) ) { - @mysqlslaves = select_array "SHOW REPLICAS\\G"; - } - else { - @mysqlslaves = select_array("SHOW SLAVE HOSTS\\G"); - } - - my @lineitems = (); - foreach my $line (@mysqlslaves) { - debugprint "L: $line "; - @lineitems = split /\s+/, $line; - $myslaves{ $lineitems[0] } = $line; - $result{'Replication'}{'Slaves'}{ $lineitems[0] } = $lineitems[4]; - } -} - -sub remove_cr { - return map { - my $line = $_; - $line =~ s/\n$//g; - $line =~ s/^\s+$//g; - $line; - } @_; -} - -sub remove_empty { - grep { $_ ne '' } @_; -} - -sub grep_file_contents { - my $file = shift; - my $patt; -} - -sub get_file_contents { - my $file = shift; - open( my $fh, "<", $file ) or die "Can't open $file for read: $!"; - my @lines = <$fh>; - close $fh or die "Cannot close $file: $!"; - @lines = remove_cr @lines; - return @lines; -} - -sub get_basic_passwords { - return get_file_contents(shift); -} - -sub get_log_file_real_path { - my $file = shift; - my $hostname = shift; - my $datadir = shift; - if ( -f "$file" ) { - return $file; - } - elsif ( -f "$hostname.log" ) { - return "$hostname.log"; - } - elsif ( -f "$hostname.err" ) { - return "$hostname.err"; - } - elsif ( -f "$datadir$hostname.err" ) { - return "$datadir$hostname.err"; - } - elsif ( -f "$datadir$hostname.log" ) { - return "$datadir$hostname.log"; - } - elsif ( -f "$datadir" . "mysql_error.log" ) { - return "$datadir" . "mysql_error.log"; - } - elsif ( -f "/var/log/mysql.log" ) { - return "/var/log/mysql.log"; - } - elsif ( -f "/var/log/mysqld.log" ) { - return "/var/log/mysqld.log"; - } - elsif ( -f "/var/log/mysql/$hostname.err" ) { - return "/var/log/mysql/$hostname.err"; - } - elsif ( -f "/var/log/mysql/$hostname.log" ) { - return "/var/log/mysql/$hostname.log"; - } - elsif ( -f "/var/log/mysql/" . "mysql_error.log" ) { - return "/var/log/mysql/" . "mysql_error.log"; - } - else { - return $file; - } -} - -sub log_file_recommendations { - if ( is_remote eq 1 ) { - infoprint "Skipping error log files checks on remote host"; - return; - } - my $fh; - $myvar{'log_error'} = $opt{'server-log'} - || get_log_file_real_path( $myvar{'log_error'}, $myvar{'hostname'}, - $myvar{'datadir'} ); - - subheaderprint "Log file Recommendations"; - if ( "$myvar{'log_error'}" eq "stderr" ) { - badprint - "log_error is set to $myvar{'log_error'}, but this script can't read stderr"; - return; - } - elsif ( $myvar{'log_error'} =~ /^(docker|podman|kubectl):(.*)/ ) { - open( $fh, '-|', "$1 logs --tail=$maxlines '$2'" ) - // die "Can't start $1 $!"; - goodprint "Log from cloud` $myvar{'log_error'} exists"; - } - elsif ( $myvar{'log_error'} =~ /^systemd:(.*)/ ) { - open( $fh, '-|', "journalctl -n $maxlines -b -u '$1'" ) - // die "Can't start journalctl $!"; - goodprint "Log journal` $myvar{'log_error'} exists"; - } - elsif ( -f "$myvar{'log_error'}" ) { - goodprint "Log file $myvar{'log_error'} exists"; - my $size = ( stat $myvar{'log_error'} )[7]; - infoprint "Log file: " - . $myvar{'log_error'} . " (" - . hr_bytes_rnd($size) . ")"; - - if ( $size > 0 ) { - goodprint "Log file $myvar{'log_error'} is not empty"; - if ( $size < 32 * 1024 * 1024 ) { - goodprint "Log file $myvar{'log_error'} is smaller than 32 MB"; - } - else { - badprint "Log file $myvar{'log_error'} is bigger than 32 MB"; - push @generalrec, - $myvar{'log_error'} - . " is > 32MB, you should analyze why or implement a rotation log strategy such as logrotate!"; - } - } - else { - infoprint - "Log file $myvar{'log_error'} is empty. Assuming log-rotation. Use --server-log={file} for explicit file"; - return; - } - if ( !open( $fh, '<', $myvar{'log_error'} ) ) { - badprint "Log file $myvar{'log_error'} isn't readable."; - return; - } - goodprint "Log file $myvar{'log_error'} is readable."; - - if ( $maxlines * 80 < $size ) { - seek( $fh, -$maxlines * 80, 2 ); - <$fh>; # discard line fragment - } - } - else { - badprint "Log file $myvar{'log_error'} doesn't exist"; - return; - } - - my $numLi = 0; - my $nbWarnLog = 0; - my $nbErrLog = 0; - my @lastShutdowns; - my @lastStarts; - - while ( my $logLi = <$fh> ) { - chomp $logLi; - $numLi++; - debugprint "$numLi: $logLi" if $logLi =~ /\[(warning|error)\]/i; - $nbErrLog++ if $logLi =~ /\[error\]/i; - $nbWarnLog++ if $logLi =~ /\[warning\]/i; - push @lastShutdowns, $logLi - if $logLi =~ /Shutdown complete/ and $logLi !~ /Innodb/i; - push @lastStarts, $logLi if $logLi =~ /ready for connections/; - } - close $fh; - - if ( $nbWarnLog > 0 ) { - badprint "$myvar{'log_error'} contains $nbWarnLog warning(s)."; - push @generalrec, "Check warning line(s) in $myvar{'log_error'} file"; - } - else { - goodprint "$myvar{'log_error'} doesn't contain any warning."; - } - if ( $nbErrLog > 0 ) { - badprint "$myvar{'log_error'} contains $nbErrLog error(s)."; - push @generalrec, "Check error line(s) in $myvar{'log_error'} file"; - } - else { - goodprint "$myvar{'log_error'} doesn't contain any error."; - } - - infoprint scalar @lastStarts . " start(s) detected in $myvar{'log_error'}"; - my $nStart = 0; - my $nEnd = 10; - if ( scalar @lastStarts < $nEnd ) { - $nEnd = scalar @lastStarts; - } - for my $startd ( reverse @lastStarts[ -$nEnd .. -1 ] ) { - $nStart++; - infoprint "$nStart) $startd"; - } - infoprint scalar @lastShutdowns - . " shutdown(s) detected in $myvar{'log_error'}"; - $nStart = 0; - $nEnd = 10; - if ( scalar @lastShutdowns < $nEnd ) { - $nEnd = scalar @lastShutdowns; - } - for my $shutd ( reverse @lastShutdowns[ -$nEnd .. -1 ] ) { - $nStart++; - infoprint "$nStart) $shutd"; - } - - #exit 0; -} - -sub cve_recommendations { - subheaderprint "CVE Security Recommendations"; - unless ( defined( $opt{cvefile} ) && -f "$opt{cvefile}" ) { - infoprint "Skipped due to --cvefile option undefined"; - return; - } - - #$mysqlvermajor=10; - #$mysqlverminor=1; - #$mysqlvermicro=17; - #prettyprint "Look for related CVE for $myvar{'version'} or lower in $opt{cvefile}"; - my $cvefound = 0; - open( my $fh, "<", $opt{cvefile} ) - or die "Can't open $opt{cvefile} for read: $!"; - while ( my $cveline = <$fh> ) { - my @cve = split( ';', $cveline ); - debugprint - "Comparing $mysqlvermajor\.$mysqlverminor\.$mysqlvermicro with $cve[1]\.$cve[2]\.$cve[3] : " - . ( mysql_version_le( $cve[1], $cve[2], $cve[3] ) ? '<=' : '>' ); - - # Avoid not major/minor version corresponding CVEs - next - unless ( int( $cve[1] ) == $mysqlvermajor - && int( $cve[2] ) == $mysqlverminor ); - if ( int( $cve[3] ) >= $mysqlvermicro ) { - badprint "$cve[4](<= $cve[1]\.$cve[2]\.$cve[3]) : $cve[6]"; - $result{'CVE'}{'List'}{$cvefound} = - "$cve[4](<= $cve[1]\.$cve[2]\.$cve[3]) : $cve[6]"; - $cvefound++; - } - } - close $fh or die "Cannot close $opt{cvefile}: $!"; - $result{'CVE'}{'nb'} = $cvefound; - - my $cve_warning_notes = ""; - if ( $cvefound == 0 ) { - goodprint "NO SECURITY CVE FOUND FOR YOUR VERSION"; - return; - } - if ( $mysqlvermajor eq 5 and $mysqlverminor eq 5 ) { - infoprint - "False positive CVE(s) for MySQL and MariaDB 5.5.x can be found."; - infoprint "Check carefully each CVE for those particular versions"; - } - badprint $cvefound . " CVE(s) found for your MySQL release."; - push( @generalrec, - $cvefound - . " CVE(s) found for your MySQL release. Consider upgrading your version !" - ); -} - -sub get_opened_ports { - my @opened_ports = `netstat -ltn`; - @opened_ports = map { - my $v = $_; - $v =~ s/.*:(\d+)\s.*$/$1/; - $v =~ s/\D//g; - $v; - } @opened_ports; - @opened_ports = sort { $a <=> $b } grep { !/^$/ } @opened_ports; - - #debugprint Dumper \@opened_ports; - $result{'Network'}{'TCP Opened'} = \@opened_ports; - return @opened_ports; -} - -sub is_open_port { - my $port = shift; - if ( grep { /^$port$/ } get_opened_ports ) { - return 1; - } - return 0; -} - -sub get_process_memory { - my $pid = shift; - my @mem = `ps -p $pid -o rss`; - return 0 if scalar @mem != 2; - return $mem[1] * 1024; -} - -sub get_other_process_memory { - return 0 if ( $opt{tbstat} == 0 ); - my @procs = `ps eaxo pid,command`; - @procs = map { - my $v = $_; - $v =~ s/.*PID.*//; - $v =~ s/.*mysqld.*//; - $v =~ s/.*\[.*\].*//; - $v =~ s/^\s+$//g; - $v =~ s/.*PID.*CMD.*//; - $v =~ s/.*systemd.*//; - $v =~ s/\s*?(\d+)\s*.*/$1/g; - $v; - } @procs; - @procs = remove_cr @procs; - @procs = remove_empty @procs; - my $totalMemOther = 0; - map { $totalMemOther += get_process_memory($_); } @procs; - return $totalMemOther; -} - -sub get_os_release { - if ( -f "/etc/lsb-release" ) { - my @info_release = get_file_contents "/etc/lsb-release"; - my $os_release = $info_release[3]; - $os_release =~ s/.*="//; - $os_release =~ s/"$//; - return $os_release; - } - - if ( -f "/etc/system-release" ) { - my @info_release = get_file_contents "/etc/system-release"; - return $info_release[0]; - } - - if ( -f "/etc/os-release" ) { - my @info_release = get_file_contents "/etc/os-release"; - my $os_release = $info_release[0]; - $os_release =~ s/.*="//; - $os_release =~ s/"$//; - return $os_release; - } - - if ( -f "/etc/issue" ) { - my @info_release = get_file_contents "/etc/issue"; - my $os_release = $info_release[0]; - $os_release =~ s/\s+\\n.*//; - return $os_release; - } - return "Unknown OS release"; -} - -sub get_fs_info { - my @sinfo = `df -P | grep '%'`; - my @iinfo = `df -Pi| grep '%'`; - shift @sinfo; - shift @iinfo; - - foreach my $info (@sinfo) { - - #exit(0); - if ( $info =~ /.*?(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(.*)$/ ) { - next if $5 =~ m{(run|dev|sys|proc|snap|init)}; - if ( $4 > 85 ) { - badprint "mount point $5 is using $4 % total space (" - . human_size( $2 * 1024 ) . " / " - . human_size( $1 * 1024 ) . ")"; - push( @generalrec, "Add some space to $4 mountpoint." ); - } - else { - infoprint "mount point $5 is using $4 % total space (" - . human_size( $2 * 1024 ) . " / " - . human_size( $1 * 1024 ) . ")"; - } - $result{'Filesystem'}{'Space Pct'}{$5} = $4; - $result{'Filesystem'}{'Used Space'}{$5} = $2; - $result{'Filesystem'}{'Free Space'}{$5} = $3; - $result{'Filesystem'}{'Total Space'}{$5} = $1; - } - } - - @iinfo = map { - my $v = $_; - $v =~ s/.*\s(\d+)%\s+(.*)/$1\t$2/g; - $v; - } @iinfo; - foreach my $info (@iinfo) { - next if $info =~ m{(\d+)\t/(run|dev|sys|proc|snap)($|/)}; - if ( $info =~ /(\d+)\t(.*)/ ) { - if ( $1 > 85 ) { - badprint "mount point $2 is using $1 % of max allowed inodes"; - push( @generalrec, - "Cleanup files from $2 mountpoint or reformat your filesystem." - ); - } - else { - infoprint "mount point $2 is using $1 % of max allowed inodes"; - } - $result{'Filesystem'}{'Inode Pct'}{$2} = $1; - } - } -} - -sub merge_hash { - my $h1 = shift; - my $h2 = shift; - my %result = {}; - foreach my $substanceref ( $h1, $h2 ) { - while ( my ( $k, $v ) = each %$substanceref ) { - next if ( exists $result{$k} ); - $result{$k} = $v; - } - } - return \%result; -} - -sub is_virtual_machine { - if ( $^O eq 'linux' ) { - my $isVm = `grep -Ec '^flags.*\ hypervisor\ ' /proc/cpuinfo`; - return ( $isVm == 0 ? 0 : 1 ); - } - - if ( $^O eq 'freebsd' ) { - my $isVm = `sysctl -n kern.vm_guest`; - chomp $isVm; - print "FARK DEBUG isVm=[$isVm]"; - return ( $isVm eq 'none' ? 0 : 1 ); - } - return 0; -} - -sub infocmd { - my $cmd = "@_"; - debugprint "CMD: $cmd"; - my @result = `$cmd`; - @result = remove_cr @result; - for my $l (@result) { - infoprint "$l"; - } -} - -sub infocmd_tab { - my $cmd = "@_"; - debugprint "CMD: $cmd"; - my @result = `$cmd`; - @result = remove_cr @result; - for my $l (@result) { - infoprint "\t$l"; - } -} - -sub infocmd_one { - my $cmd = "@_"; - my @result = `$cmd 2>&1`; - @result = remove_cr @result; - return join ', ', @result; -} - -sub get_kernel_info { - my @params = ( - 'fs.aio-max-nr', 'fs.aio-nr', - 'fs.nr_open', 'fs.file-max', - 'sunrpc.tcp_fin_timeout', 'sunrpc.tcp_max_slot_table_entries', - 'sunrpc.tcp_slot_table_entries', 'vm.swappiness' - ); - infoprint "Information about kernel tuning:"; - foreach my $param (@params) { - infocmd_tab("sysctl $param 2>/dev/null"); - $result{'OS'}{'Config'}{$param} = `sysctl -n $param 2>/dev/null`; - } - if ( `sysctl -n vm.swappiness` > 10 ) { - badprint - "Swappiness is > 10, please consider having a value lower than 10"; - push @generalrec, "setup swappiness lower or equal to 10"; - push @adjvars, - 'vm.swappiness <= 10 (echo 10 > /proc/sys/vm/swappiness) or vm.swappiness=10 in /etc/sysctl.conf'; - } - else { - infoprint "Swappiness is < 10."; - } - - # only if /proc/sys/sunrpc exists - my $tcp_slot_entries = - `sysctl -n sunrpc.tcp_slot_table_entries 2>/dev/null`; - if ( -f "/proc/sys/sunrpc" - and ( $tcp_slot_entries eq '' or $tcp_slot_entries < 100 ) ) - { - badprint - "Initial TCP slot entries is < 1M, please consider having a value greater than 100"; - push @generalrec, "setup Initial TCP slot entries greater than 100"; - push @adjvars, - 'sunrpc.tcp_slot_table_entries > 100 (echo 128 > /proc/sys/sunrpc/tcp_slot_table_entries) or sunrpc.tcp_slot_table_entries=128 in /etc/sysctl.conf'; - } - else { - infoprint "TCP slot entries is > 100."; - } - - if ( -f "/proc/sys/fs/aio-max-nr" ) { - if ( `sysctl -n fs.aio-max-nr` < 1000000 ) { - badprint - "Max running total of the number of max. events is < 1M, please consider having a value greater than 1M"; - push @generalrec, "setup Max running number events greater than 1M"; - push @adjvars, - 'fs.aio-max-nr > 1M (echo 1048576 > /proc/sys/fs/aio-max-nr) or fs.aio-max-nr=1048576 in /etc/sysctl.conf'; - } - else { - infoprint "Max Number of AIO events is > 1M."; - } - } - if ( -f "/proc/sys/fs/nr_open" ) { - if ( `sysctl -n fs.nr_open` < 1000000 ) { - badprint - "Max running total of the number of file open request is < 1M, please consider having a value greater than 1M"; - push @generalrec, - "setup running number of open request greater than 1M"; - push @adjvars, - 'fs.aio-nr > 1M (echo 1048576 > /proc/sys/fs/nr_open) or fs.nr_open=1048576 in /etc/sysctl.conf'; - } - else { - infoprint "Max Number of open file requests is > 1M."; - } - } -} - -sub get_system_info { - $result{'OS'}{'Release'} = get_os_release(); - infoprint get_os_release; - if (is_virtual_machine) { - infoprint "Machine type : Virtual machine"; - $result{'OS'}{'Virtual Machine'} = 'YES'; - } - else { - infoprint "Machine type : Physical machine"; - $result{'OS'}{'Virtual Machine'} = 'NO'; - } - - $result{'Network'}{'Connected'} = 'NO'; - `ping -c 1 ipecho.net &>/dev/null`; - my $isConnected = $?; - if ( $? == 0 ) { - infoprint "Internet : Connected"; - $result{'Network'}{'Connected'} = 'YES'; - } - else { - badprint "Internet : Disconnected"; - } - $result{'OS'}{'NbCore'} = cpu_cores; - infoprint "Number of Core CPU : " . cpu_cores; - $result{'OS'}{'Type'} = `uname -o`; - infoprint "Operating System Type : " . infocmd_one "uname -o"; - $result{'OS'}{'Kernel'} = `uname -r`; - infoprint "Kernel Release : " . infocmd_one "uname -r"; - $result{'OS'}{'Hostname'} = `hostname`; - $result{'Network'}{'Internal Ip'} = `hostname -I`; - infoprint "Hostname : " . infocmd_one "hostname"; - infoprint "Network Cards : "; - infocmd_tab "ifconfig| grep -A1 mtu"; - infoprint "Internal IP : " . infocmd_one "hostname -I"; - $result{'Network'}{'Internal Ip'} = `ifconfig| grep -A1 mtu`; - my $httpcli = get_http_cli(); - infoprint "HTTP client found: $httpcli" if defined $httpcli; - - my $ext_ip = ""; - if ( $httpcli =~ /curl$/ ) { - $ext_ip = infocmd_one "$httpcli -m 3 ipecho.net/plain"; - } - elsif ( $httpcli =~ /wget$/ ) { - - $ext_ip = infocmd_one "$httpcli -t 1 -T 3 -q -O - ipecho.net/plain"; - } - infoprint "External IP : " . $ext_ip; - $result{'Network'}{'External Ip'} = $ext_ip; - badprint "External IP : Can't check, no Internet connectivity" - unless defined($httpcli); - infoprint "Name Servers : " - . infocmd_one "grep 'nameserver' /etc/resolv.conf \| awk '{print \$2}'"; - infoprint "Logged In users : "; - infocmd_tab "who"; - $result{'OS'}{'Logged users'} = `who`; - infoprint "Ram Usages in MB : "; - infocmd_tab "free -m | grep -v +"; - $result{'OS'}{'Free Memory RAM'} = `free -m | grep -v +`; - infoprint "Load Average : "; - infocmd_tab "top -n 1 -b | grep 'load average:'"; - $result{'OS'}{'Load Average'} = `top -n 1 -b | grep 'load average:'`; - - infoprint "System Uptime : "; - infocmd_tab "uptime"; - $result{'OS'}{'Uptime'} = `uptime`; -} - -sub system_recommendations { - if ( is_remote eq 1 ) { - infoprint "Skipping system checks on remote host"; - return; - } - return if ( $opt{sysstat} == 0 ); - subheaderprint "System Linux Recommendations"; - my $os = `uname`; - unless ( $os =~ /Linux/i ) { - infoprint "Skipped due to non Linux server"; - return; - } - prettyprint "Look for related Linux system recommendations"; - - #prettyprint '-'x78; - get_system_info(); - - my $nb_cpus = cpu_cores; - if ( $nb_cpus > 1 ) { - goodprint "There is at least one CPU dedicated to database server."; - } - else { - badprint - "There is only one CPU, consider dedicated one CPU for your database server"; - push @generalrec, - "Consider increasing number of CPU for your database server"; - } - - if ( $physical_memory >= 1.5 * 1024 ) { - goodprint "There is at least 1 Gb of RAM dedicated to Linux server."; - } - else { - badprint - "There is less than 1,5 Gb of RAM, consider dedicated 1 Gb for your Linux server"; - push @generalrec, - "Consider increasing 1,5 / 2 Gb of RAM for your Linux server"; - } - - my $omem = get_other_process_memory; - infoprint "User process except mysqld used " - . hr_bytes_rnd($omem) . " RAM."; - if ( ( 0.15 * $physical_memory ) < $omem ) { - if ( $opt{nondedicated} ) { - infoprint "No warning with --nondedicated option"; - infoprint - "Other user process except mysqld used more than 15% of total physical memory " - . percentage( $omem, $physical_memory ) . "% (" - . hr_bytes_rnd($omem) . " / " - . hr_bytes_rnd($physical_memory) . ")"; - } - else { - - badprint - "Other user process except mysqld used more than 15% of total physical memory " - . percentage( $omem, $physical_memory ) . "% (" - . hr_bytes_rnd($omem) . " / " - . hr_bytes_rnd($physical_memory) . ")"; - push( @generalrec, - "Consider stopping or dedicate server for additional process other than mysqld." - ); - push( @adjvars, - "DON'T APPLY SETTINGS BECAUSE THERE ARE TOO MANY PROCESSES RUNNING ON THIS SERVER. OOM KILL CAN OCCUR!" - ); - } - } - else { - infoprint - "Other user process except mysqld used less than 15% of total physical memory " - . percentage( $omem, $physical_memory ) . "% (" - . hr_bytes_rnd($omem) . " / " - . hr_bytes_rnd($physical_memory) . ")"; - } - - if ( $opt{'maxportallowed'} > 0 ) { - my @opened_ports = get_opened_ports; - infoprint "There is " - . scalar @opened_ports - . " listening port(s) on this server."; - if ( scalar(@opened_ports) > $opt{'maxportallowed'} ) { - badprint "There are too many listening ports: " - . scalar(@opened_ports) - . " opened > " - . $opt{'maxportallowed'} - . "allowed."; - push( @generalrec, - "Consider dedicating a server for your database installation with fewer services running on it!" - ); - } - else { - goodprint "There are less than " - . $opt{'maxportallowed'} - . " opened ports on this server."; - } - } - - foreach my $banport (@banned_ports) { - if ( is_open_port($banport) ) { - badprint "Banned port: $banport is opened.."; - push( @generalrec, - "Port $banport is opened. Consider stopping the program over this port." - ); - } - else { - goodprint "$banport is not opened."; - } - } - - subheaderprint "Filesystem Linux Recommendations"; - get_fs_info; - subheaderprint "Kernel Information Recommendations"; - get_kernel_info; -} - -sub security_recommendations { - subheaderprint "Security Recommendations"; - - infoprint "$myvar{'version_comment'} - $myvar{'version'}"; - if ( mysql_version_ge(8.0) ) { - infoprint "Skipped due to unsupported feature for MySQL 8.0+"; - return; - } - - #exit 0; - if ( $opt{skippassword} eq 1 ) { - infoprint "Skipped due to --skippassword option"; - return; - } - - my $PASS_COLUMN_NAME = 'password'; - - # New table schema available since mysql-5.7 and mariadb-10.2 - # But need to be checked - if ( $myvar{'version'} =~ /5\.7|10\.[2-5]\..*MariaDB*/ ) { - my $password_column_exists = - `$mysqlcmd $mysqllogin -Bse "SELECT 1 FROM information_schema.columns WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME = 'password'" 2>>/dev/null`; - my $authstring_column_exists = - `$mysqlcmd $mysqllogin -Bse "SELECT 1 FROM information_schema.columns WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME = 'authentication_string'" 2>>/dev/null`; - if ( $password_column_exists && $authstring_column_exists ) { - $PASS_COLUMN_NAME = - "IF(plugin='mysql_native_password', authentication_string, password)"; - } - elsif ($authstring_column_exists) { - $PASS_COLUMN_NAME = 'authentication_string'; - } - elsif ( !$password_column_exists ) { - infoprint "Skipped due to none of known auth columns exists"; - return; - } - } - debugprint "Password column = $PASS_COLUMN_NAME"; - - # IS THERE A ROLE COLUMN - my $is_role_column = select_one - "select count(*) from information_schema.columns where TABLE_NAME='user' AND TABLE_SCHEMA='mysql' and COLUMN_NAME='IS_ROLE'"; - - my $extra_user_condition = ""; - $extra_user_condition = "IS_ROLE = 'N' AND" if $is_role_column > 0; - my @mysqlstatlist; - if ( $is_role_column > 0 ) { - @mysqlstatlist = select_array - "SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE IS_ROLE='Y'"; - foreach my $line ( sort @mysqlstatlist ) { - chomp($line); - infoprint "User $line is User Role"; - } - } - else { - debugprint "No Role user detected"; - goodprint "No Role user detected"; - } - - # Looking for Anonymous users - @mysqlstatlist = select_array - "SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE $extra_user_condition (TRIM(USER) = '' OR USER IS NULL)"; - - #debugprint Dumper \@mysqlstatlist; - - #exit 0; - if (@mysqlstatlist) { - push( @generalrec, - "Remove Anonymous User accounts: there are " - . scalar(@mysqlstatlist) - . " anonymous accounts." ); - foreach my $line ( sort @mysqlstatlist ) { - chomp($line); - badprint "User " - . $line - . " is an anonymous account. Remove with DROP USER " - . $line . ";"; - } - } - else { - goodprint "There are no anonymous accounts for any database users"; - } - if ( mysql_version_le( 5, 1 ) ) { - badprint "No more password checks for MySQL version <=5.1"; - badprint "MySQL version <=5.1 is deprecated and end of support."; - return; - } - - # Looking for Empty Password - if ( mysql_version_ge( 10, 4 ) ) { - @mysqlstatlist = select_array - q{SELECT CONCAT(QUOTE(user), '@', QUOTE(host)) FROM mysql.global_priv WHERE - ( user != '' - AND JSON_CONTAINS(Priv, '"mysql_native_password"', '$.plugin') AND JSON_CONTAINS(Priv, '""', '$.authentication_string') - AND NOT JSON_CONTAINS(Priv, 'true', '$.account_locked') - )}; - } - else { - @mysqlstatlist = select_array - "SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE ($PASS_COLUMN_NAME = '' OR $PASS_COLUMN_NAME IS NULL) - AND user != '' - /*!50501 AND plugin NOT IN ('auth_socket', 'unix_socket', 'win_socket', 'auth_pam_compat') */ - /*!80000 AND account_locked = 'N' AND password_expired = 'N' */"; - } - if (@mysqlstatlist) { - foreach my $line ( sort @mysqlstatlist ) { - chomp($line); - badprint "User '" . $line . "' has no password set."; - push( @generalrec, - "Set up a Secure Password for $line user: SET PASSWORD FOR $line = PASSWORD('secure_password');" - ); - } - } - else { - goodprint "All database users have passwords assigned"; - } - - if ( mysql_version_ge( 5, 7 ) ) { - my $valPlugin = select_one( - "select count(*) from information_schema.plugins where PLUGIN_NAME='validate_password' AND PLUGIN_STATUS='ACTIVE'" - ); - if ( $valPlugin >= 1 ) { - infoprint - "Bug #80860 MySQL 5.7: Avoid testing password when validate_password is activated"; - return; - } - } - - # Looking for User with user/ uppercase /capitalise user as password - @mysqlstatlist = select_array - "SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE user != '' AND (CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(user) OR CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(UPPER(user)) OR CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(CONCAT(UPPER(LEFT(User, 1)), SUBSTRING(User, 2, LENGTH(User)))))"; - if (@mysqlstatlist) { - foreach my $line ( sort @mysqlstatlist ) { - chomp($line); - badprint "User " . $line . " has user name as password."; - push( @generalrec, - "Set up a Secure Password for $line user: SET PASSWORD FOR $line = PASSWORD('secure_password');" - ); - } - } - - @mysqlstatlist = select_array - "SELECT CONCAT(QUOTE(user), '\@', host) FROM mysql.user WHERE HOST='%'"; - if (@mysqlstatlist) { - foreach my $line ( sort @mysqlstatlist ) { - chomp($line); - my $luser = ( split /@/, $line )[0]; - badprint "User " . $line - . " does not specify hostname restrictions."; - push( @generalrec, - "Restrict Host for $luser\@'%' to $luser\@LimitedIPRangeOrLocalhost" - ); - push( @generalrec, - "RENAME USER $luser\@'%' TO " - . $luser - . "\@LimitedIPRangeOrLocalhost;" ); - } - } - - unless ( -f $basic_password_files ) { - badprint "There is no basic password file list!"; - return; - } - - my @passwords = get_basic_passwords $basic_password_files; - infoprint "There are " - . scalar(@passwords) - . " basic passwords in the list."; - my $nbins = 0; - my $passreq; - if (@passwords) { - my $nbInterPass = 0; - foreach my $pass (@passwords) { - $nbInterPass++; - - $pass =~ s/\s//g; - $pass =~ s/\'/\\\'/g; - chomp($pass); - - # Looking for User with user/ uppercase /capitalise weak password - @mysqlstatlist = - select_array - "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE $PASS_COLUMN_NAME = PASSWORD('" - . $pass - . "') OR $PASS_COLUMN_NAME = PASSWORD(UPPER('" - . $pass - . "')) OR $PASS_COLUMN_NAME = PASSWORD(CONCAT(UPPER(LEFT('" - . $pass - . "', 1)), SUBSTRING('" - . $pass - . "', 2, LENGTH('" - . $pass . "'))))"; - debugprint "There are " . scalar(@mysqlstatlist) . " items."; - if (@mysqlstatlist) { - foreach my $line (@mysqlstatlist) { - chomp($line); - badprint "User '" . $line - . "' is using weak password: $pass in a lower, upper or capitalize derivative version."; - - push( @generalrec, - "Set up a Secure Password for $line user: SET PASSWORD FOR '" - . ( split /@/, $line )[0] . "'\@'" - . ( split /@/, $line )[1] - . "' = PASSWORD('secure_password');" ); - $nbins++; - } - } - debugprint "$nbInterPass / " . scalar(@passwords) - if ( $nbInterPass % 1000 == 0 ); - } - } - if ( $nbins > 0 ) { - push( @generalrec, - $nbins - . " user(s) used basic or weak password from basic dictionary." ); - } -} - -sub get_replication_status { - subheaderprint "Replication Metrics"; - infoprint "Galera Synchronous replication: " . $myvar{'have_galera'}; - if ( scalar( keys %myslaves ) == 0 ) { - infoprint "No replication slave(s) for this server."; - } - else { - infoprint "This server is acting as master for " - . scalar( keys %myslaves ) - . " server(s)."; - } - infoprint "Binlog format: " . $myvar{'binlog_format'}; - infoprint "XA support enabled: " . $myvar{'innodb_support_xa'}; - - infoprint "Semi synchronous replication Master: " - . ( - ( - defined( $myvar{'rpl_semi_sync_master_enabled'} ) - or defined( $myvar{'rpl_semi_sync_source_enabled'} ) - ) - ? ( $myvar{'rpl_semi_sync_master_enabled'} - // $myvar{'rpl_semi_sync_source_enabled'} ) - : 'Not Activated' - ); - infoprint "Semi synchronous replication Slave: " - . ( - ( - defined( $myvar{'rpl_semi_sync_slave_enabled'} ) - or defined( $myvar{'rpl_semi_sync_replica_enabled'} ) - ) - ? ( $myvar{'rpl_semi_sync_slave_enabled'} - // $myvar{'rpl_semi_sync_replica_enabled'} ) - : 'Not Activated' - ); - if ( scalar( keys %myrepl ) == 0 and scalar( keys %myslaves ) == 0 ) { - infoprint "This is a standalone server"; - return; - } - if ( scalar( keys %myrepl ) == 0 ) { - infoprint - "No replication setup for this server or replication not started."; - return; - } - - $result{'Replication'}{'status'} = \%myrepl; - my ($io_running) = $myrepl{'Slave_IO_Running'} - // $myrepl{'Replica_IO_Running'}; - debugprint "IO RUNNING: $io_running "; - my ($sql_running) = $myrepl{'Slave_SQL_Running'} - // $myrepl{'Replica_SQL_Running'}; - debugprint "SQL RUNNING: $sql_running "; - - my ($seconds_behind_master) = $myrepl{'Seconds_Behind_Master'} - // $myrepl{'Seconds_Behind_Source'}; - $seconds_behind_master = 1000000 unless defined($seconds_behind_master); - debugprint "SECONDS : $seconds_behind_master "; - - if ( defined($io_running) - and ( $io_running !~ /yes/i or $sql_running !~ /yes/i ) ) - { - badprint - "This replication slave is not running but seems to be configured."; - } - if ( defined($io_running) - && $io_running =~ /yes/i - && $sql_running =~ /yes/i ) - { - if ( $myvar{'read_only'} eq 'OFF' ) { - badprint - "This replication slave is running with the read_only option disabled."; - } - else { - goodprint - "This replication slave is running with the read_only option enabled."; - } - if ( $seconds_behind_master > 0 ) { - badprint - "This replication slave is lagging and slave has $seconds_behind_master second(s) behind master host."; - } - else { - goodprint "This replication slave is up to date with master."; - } - } -} - -# https://endoflife.software/applications/databases/mysql -# https://endoflife.date/mariadb -sub validate_mysql_version { - ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = - $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; - $mysqlverminor ||= 0; - $mysqlvermicro ||= 0; - - prettyprint " "; - - if ( mysql_version_eq(9,1) - or mysql_version_eq(8, 4) - or mysql_version_eq(8, 0) - or mysql_version_eq( 10, 5 ) - or mysql_version_eq( 10, 6 ) - or mysql_version_eq( 10, 11 ) - or mysql_version_eq( 11, 4 ) - or mysql_version_eq( 11, 6 ) ) - { - goodprint "Currently running supported MySQL version " - . $myvar{'version'} . ""; - return; - } - else { - badprint "Your MySQL version " - . $myvar{'version'} - . " is EOL software. Upgrade soon!"; - push( @generalrec, - "You are using an unsupported version for production environments" - ); - push( @generalrec, - "Upgrade as soon as possible to a supported version !" ); - - } -} - -# Checks if MySQL version is equal to (major, minor, micro) -sub mysql_version_eq { - my ( $maj, $min, $mic ) = @_; - my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = - $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; - - return int($mysqlvermajor) == int($maj) - if ( !defined($min) && !defined($mic) ); - return int($mysqlvermajor) == int($maj) && int($mysqlverminor) == int($min) - if ( !defined($mic) ); - return ( int($mysqlvermajor) == int($maj) - && int($mysqlverminor) == int($min) - && int($mysqlvermicro) == int($mic) ); -} - -# Checks if MySQL version is greater than equal to (major, minor, micro) -sub mysql_version_ge { - my ( $maj, $min, $mic ) = @_; - $min ||= 0; - $mic ||= 0; - my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = - $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; - - return - int($mysqlvermajor) > int($maj) - || ( int($mysqlvermajor) == int($maj) && int($mysqlverminor) > int($min) ) - || ( int($mysqlvermajor) == int($maj) - && int($mysqlverminor) == int($min) - && int($mysqlvermicro) >= int($mic) ); -} - -# Checks if MySQL version is lower than equal to (major, minor, micro) -sub mysql_version_le { - my ( $maj, $min, $mic ) = @_; - $min ||= 0; - $mic ||= 0; - my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = - $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; - #infoprint "MySQL version: $mysqlvermajor.$mysqlverminor.$mysqlvermicro"; - - return - int($mysqlvermajor) < int($maj) - || ( int($mysqlvermajor) == int($maj) && int($mysqlverminor) < int($min) ) - || ( int($mysqlvermajor) == int($maj) - && int($mysqlverminor) == int($min) - && int($mysqlvermicro) <= int($mic) ); -} - -# Checks for 32-bit boxes with more than 2GB of RAM -my ($arch); - -sub check_architecture { - if ( is_remote eq 1 ) { - infoprint "Skipping architecture check on remote host"; - infoprint "Using default $opt{defaultarch} bits as target architecture"; - $arch = $opt{defaultarch}; - return; - } - if ( `uname` =~ /SunOS/ && `isainfo -b` =~ /64/ ) { - $arch = 64; - goodprint "Operating on 64-bit architecture"; - } - elsif ( `uname` !~ /SunOS/ && `uname -m` =~ /(64|s390x)/ ) { - $arch = 64; - goodprint "Operating on 64-bit architecture"; - } - elsif ( `uname` =~ /AIX/ && `bootinfo -K` =~ /64/ ) { - $arch = 64; - goodprint "Operating on 64-bit architecture"; - } - elsif ( `uname` =~ /NetBSD|OpenBSD/ && `sysctl -b hw.machine` =~ /64/ ) { - $arch = 64; - goodprint "Operating on 64-bit architecture"; - } - elsif ( `uname` =~ /FreeBSD/ && `sysctl -b hw.machine_arch` =~ /64/ ) { - $arch = 64; - goodprint "Operating on 64-bit architecture"; - } - elsif ( `uname` =~ /Darwin/ && `uname -m` =~ /Power Macintosh/ ) { - - # Darwin box.local 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:57:01 PDT 2009; root:xnu1228.15.4~1/RELEASE_PPC Power Macintosh - $arch = 64; - goodprint "Operating on 64-bit architecture"; - } - elsif ( `uname` =~ /Darwin/ && `uname -m` =~ /x86_64/ ) { - - # Darwin gibas.local 12.6.0 Darwin Kernel Version 12.3.0: Sun Jan 6 22:37:10 PST 2013; root:xnu-2050.22.13~1/RELEASE_X86_64 x86_64 - $arch = 64; - goodprint "Operating on 64-bit architecture"; - } - else { - $arch = 32; - if ( $physical_memory > 2147483648 ) { - badprint - "Switch to 64-bit OS - MySQL cannot currently use all of your RAM"; - } - else { - goodprint "Operating on 32-bit architecture with less than 2GB RAM"; - } - } - $result{'OS'}{'Architecture'} = "$arch bits"; - -} - -# Start up a ton of storage engine counts/statistics -my ( %enginestats, %enginecount, $fragtables ); - -sub check_storage_engines { - subheaderprint "Storage Engine Statistics"; - if ( $opt{skipsize} eq 1 ) { - infoprint "Skipped due to --skipsize option"; - return; - } - - my $engines; - if ( mysql_version_ge( 5, 5 ) ) { - my @engineresults = select_array - "SELECT ENGINE,SUPPORT FROM information_schema.ENGINES ORDER BY ENGINE ASC"; - foreach my $line (@engineresults) { - my ( $engine, $engineenabled ); - ( $engine, $engineenabled ) = $line =~ /([a-zA-Z_]*)\s+([a-zA-Z]+)/; - $result{'Engine'}{$engine}{'Enabled'} = $engineenabled; - $engines .= - ( $engineenabled eq "YES" || $engineenabled eq "DEFAULT" ) - ? greenwrap "+" . $engine . " " - : redwrap "-" . $engine . " "; - } - } - elsif ( mysql_version_ge( 5, 1, 5 ) ) { - my @engineresults = select_array - "SELECT ENGINE, SUPPORT FROM information_schema.ENGINES WHERE ENGINE NOT IN ('MyISAM', 'MERGE', 'MEMORY') ORDER BY ENGINE"; - foreach my $line (@engineresults) { - my ( $engine, $engineenabled ); - ( $engine, $engineenabled ) = $line =~ /([a-zA-Z_]*)\s+([a-zA-Z]+)/; - $result{'Engine'}{$engine}{'Enabled'} = $engineenabled; - $engines .= - ( $engineenabled eq "YES" || $engineenabled eq "DEFAULT" ) - ? greenwrap "+" . $engine . " " - : redwrap "-" . $engine . " "; - } - } - else { - $engines .= - ( defined $myvar{'have_archive'} && $myvar{'have_archive'} eq "YES" ) - ? greenwrap "+Archive " - : redwrap "-Archive "; - $engines .= - ( defined $myvar{'have_bdb'} && $myvar{'have_bdb'} eq "YES" ) - ? greenwrap "+BDB " - : redwrap "-BDB "; - $engines .= - ( defined $myvar{'have_federated_engine'} - && $myvar{'have_federated_engine'} eq "YES" ) - ? greenwrap "+Federated " - : redwrap "-Federated "; - $engines .= - ( defined $myvar{'have_innodb'} && $myvar{'have_innodb'} eq "YES" ) - ? greenwrap "+InnoDB " - : redwrap "-InnoDB "; - $engines .= - ( defined $myvar{'have_isam'} && $myvar{'have_isam'} eq "YES" ) - ? greenwrap "+ISAM " - : redwrap "-ISAM "; - $engines .= - ( defined $myvar{'have_ndbcluster'} - && $myvar{'have_ndbcluster'} eq "YES" ) - ? greenwrap "+NDBCluster " - : redwrap "-NDBCluster "; - } - - my @dblist = grep { $_ ne 'lost+found' } select_array "SHOW DATABASES"; - - $result{'Databases'}{'List'} = [@dblist]; - infoprint "Status: $engines"; - if ( mysql_version_ge( 5, 1, 5 ) ) { - - # MySQL 5+ servers can have table sizes calculated quickly from information schema - my @templist = select_array - "SELECT ENGINE, SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(ENGINE), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND ENGINE IS NOT NULL GROUP BY ENGINE ORDER BY ENGINE ASC;"; - - my ( $engine, $size, $count, $dsize, $isize ); - foreach my $line (@templist) { - ( $engine, $size, $count, $dsize, $isize ) = - $line =~ /([a-zA-Z_]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/; - debugprint "Engine Found: $engine"; - next unless ( defined($engine) or trim($engine) eq '' ); - $size = 0 unless ( defined($size) or trim($engine) eq '' ); - $isize = 0 unless ( defined($isize) or trim($engine) eq '' ); - $dsize = 0 unless ( defined($dsize) or trim($engine) eq '' ); - $count = 0 unless ( defined($count) or trim($engine) eq '' ); - $enginestats{$engine} = $size; - $enginecount{$engine} = $count; - $result{'Engine'}{$engine}{'Table Number'} = $count; - $result{'Engine'}{$engine}{'Total Size'} = $size; - $result{'Engine'}{$engine}{'Data Size'} = $dsize; - $result{'Engine'}{$engine}{'Index Size'} = $isize; - } - - #print Dumper( \%enginestats ) if $opt{debug}; - my $not_innodb = ''; - if ( not defined $result{'Variables'}{'innodb_file_per_table'} ) { - $not_innodb = "AND NOT ENGINE='InnoDB'"; - } - elsif ( $result{'Variables'}{'innodb_file_per_table'} eq 'OFF' ) { - $not_innodb = "AND NOT ENGINE='InnoDB'"; - } - $result{'Tables'}{'Fragmented tables'} = - [ select_array - "SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, CAST(DATA_FREE AS SIGNED) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND DATA_LENGTH/1024/1024>100 AND cast(DATA_FREE as signed)*100/(DATA_LENGTH+INDEX_LENGTH+cast(DATA_FREE as signed)) > 10 AND NOT ENGINE='MEMORY' $not_innodb" - ]; - $fragtables = scalar @{ $result{'Tables'}{'Fragmented tables'} }; - - } - else { - - # MySQL < 5 servers take a lot of work to get table sizes - my @tblist; - - # Now we build a database list, and loop through it to get storage engine stats for tables - foreach my $db (@dblist) { - chomp($db); - if ( $db eq "information_schema" - or $db eq "performance_schema" - or $db eq "mysql" - or $db eq "lost+found" ) - { - next; - } - my @ixs = ( 1, 6, 9 ); - if ( !mysql_version_ge( 4, 1 ) ) { - - # MySQL 3.23/4.0 keeps Data_Length in the 5th (0-based) column - @ixs = ( 1, 5, 8 ); - } - push( @tblist, - map { [ (split)[@ixs] ] } - select_array "SHOW TABLE STATUS FROM \\\`$db\\\`" ); - } - - # Parse through the table list to generate storage engine counts/statistics - $fragtables = 0; - foreach my $tbl (@tblist) { - - #debugprint "Data dump " . Dumper(@$tbl) if $opt{debug}; - my ( $engine, $size, $datafree ) = @$tbl; - next if $engine eq 'NULL' or not defined($engine); - $size = 0 if $size eq 'NULL' or not defined($size); - $datafree = 0 if $datafree eq 'NULL' or not defined($datafree); - if ( defined $enginestats{$engine} ) { - $enginestats{$engine} += $size; - $enginecount{$engine} += 1; - } - else { - $enginestats{$engine} = $size; - $enginecount{$engine} = 1; - } - if ( $datafree > 0 ) { - $fragtables++; - } - } - } - while ( my ( $engine, $size ) = each(%enginestats) ) { - infoprint "Data in $engine tables: " - . hr_bytes($size) - . " (Tables: " - . $enginecount{$engine} . ")" . ""; - } - - # If the storage engine isn't being used, recommend it to be disabled - if ( !defined $enginestats{'InnoDB'} - && defined $myvar{'have_innodb'} - && $myvar{'have_innodb'} eq "YES" ) - { - badprint "InnoDB is enabled, but isn't being used"; - push( @generalrec, - "Add skip-innodb to MySQL configuration to disable InnoDB" ); - } - if ( !defined $enginestats{'BerkeleyDB'} - && defined $myvar{'have_bdb'} - && $myvar{'have_bdb'} eq "YES" ) - { - badprint "BDB is enabled, but isn't being used"; - push( @generalrec, - "Add skip-bdb to MySQL configuration to disable BDB" ); - } - if ( !defined $enginestats{'ISAM'} - && defined $myvar{'have_isam'} - && $myvar{'have_isam'} eq "YES" ) - { - badprint "MyISAM is enabled, but isn't being used"; - push( @generalrec, - "Add skip-isam to MySQL configuration to disable MyISAM (MySQL > 4.1.0)" - ); - } - - # Fragmented tables - if ( $fragtables > 0 ) { - badprint "Total fragmented tables: $fragtables"; - push @generalrec, - 'Run ALTER TABLE ... FORCE or OPTIMIZE TABLE to defragment tables for better performance'; - my $total_free = 0; - foreach my $table_line ( @{ $result{'Tables'}{'Fragmented tables'} } ) { - my ( $table_schema, $table_name, $engine, $data_free ) = - split /\t/msx, $table_line; - $data_free = $data_free / 1024 / 1024; - $total_free += $data_free; - my $generalrec; - if ( $engine eq 'InnoDB' ) { - $generalrec = - " ALTER TABLE `$table_schema`.`$table_name` FORCE;"; - } - else { - $generalrec = " OPTIMIZE TABLE `$table_schema`.`$table_name`;"; - } - $generalrec .= " -- can free $data_free MiB"; - push @generalrec, $generalrec; - } - push @generalrec, - "Total freed space after defragmentation: $total_free MiB"; - } - else { - goodprint "Total fragmented tables: $fragtables"; - } - - # Auto increments - my %tblist; - - # Find the maximum integer - my $maxint = select_one "SELECT ~0"; - $result{'MaxInt'} = $maxint; - - # Now we use a database list, and loop through it to get storage engine stats for tables - foreach my $db (@dblist) { - chomp($db); - - if ( !$tblist{$db} ) { - $tblist{$db} = (); - } - - if ( $db eq "information_schema" ) { next; } - my @ia = ( 0, 10 ); - if ( !mysql_version_ge( 4, 1 ) ) { - - # MySQL 3.23/4.0 keeps Data_Length in the 5th (0-based) column - @ia = ( 0, 9 ); - } - push( - @{ $tblist{$db} }, - map { [ (split)[@ia] ] } - select_array "SHOW TABLE STATUS FROM \\\`$db\\\`" - ); - } - - my @dbnames = keys %tblist; - - foreach my $db (@dbnames) { - foreach my $tbl ( @{ $tblist{$db} } ) { - my ( $name, $autoincrement ) = @$tbl; - - if ( $autoincrement =~ /^\d+?$/ ) { - my $percent = percentage( $autoincrement, $maxint ); - $result{'PctAutoIncrement'}{"$db.$name"} = $percent; - if ( $percent >= 75 ) { - badprint - "Table '$db.$name' has an autoincrement value near max capacity ($percent%)"; - } - } - } - } -} - -my %mycalc; - -sub dump_into_file { - my $file = shift; - my $content = shift; - if ( -d "$opt{dumpdir}" ) { - $file = "$opt{dumpdir}/$file"; - open( FILE, ">$file" ) or die "Can't open $file: $!"; - print FILE $content; - close FILE; - infoprint "Data saved to $file"; - } -} - -sub calculations { - if ( $mystat{'Questions'} < 1 ) { - badprint "Your server has not answered any queries: cannot continue..."; - exit 2; - } - #infoprint "====>>>> MySQL version: $myvar{'version'}"; - $myvar{'version'} =~ s/(.+)-.*?$/$1/; - #infoprint "====>>>> MySQL version updated: $myvar{'version'}"; - # Per-thread memory - $mycalc{'per_thread_buffers'} = 0; - $mycalc{'per_thread_buffers'} += $myvar{'read_buffer_size'} - if is_int( $myvar{'read_buffer_size'} ); - $mycalc{'per_thread_buffers'} += $myvar{'read_rnd_buffer_size'} - if is_int( $myvar{'read_rnd_buffer_size'} ); - $mycalc{'per_thread_buffers'} += $myvar{'sort_buffer_size'} - if is_int( $myvar{'sort_buffer_size'} ); - $mycalc{'per_thread_buffers'} += $myvar{'thread_stack'} - if is_int( $myvar{'thread_stack'} ); - $mycalc{'per_thread_buffers'} += $myvar{'join_buffer_size'} - if is_int( $myvar{'join_buffer_size'} ); - $mycalc{'per_thread_buffers'} += $myvar{'binlog_cache_size'} - if is_int( $myvar{'binlog_cache_size'} ); - debugprint "per_thread_buffers: $mycalc{'per_thread_buffers'} (" - . human_size( $mycalc{'per_thread_buffers'} ) . " )"; - - # Error max_allowed_packet is not included in thread buffers size - #$mycalc{'per_thread_buffers'} += $myvar{'max_allowed_packet'} if is_int($myvar{'max_allowed_packet'}); - - # Total per-thread memory - $mycalc{'total_per_thread_buffers'} = - $mycalc{'per_thread_buffers'} * $myvar{'max_connections'}; - - # Max total per-thread memory reached - $mycalc{'max_total_per_thread_buffers'} = - $mycalc{'per_thread_buffers'} * $mystat{'Max_used_connections'}; - - # Server-wide memory - $mycalc{'max_tmp_table_size'} = - ( $myvar{'tmp_table_size'} > $myvar{'max_heap_table_size'} ) - ? $myvar{'max_heap_table_size'} - : $myvar{'tmp_table_size'}; - $mycalc{'server_buffers'} = - $myvar{'key_buffer_size'} + $mycalc{'max_tmp_table_size'}; - $mycalc{'server_buffers'} += - ( defined $myvar{'innodb_buffer_pool_size'} ) - ? $myvar{'innodb_buffer_pool_size'} - : 0; - $mycalc{'server_buffers'} += - ( defined $myvar{'innodb_additional_mem_pool_size'} ) - ? $myvar{'innodb_additional_mem_pool_size'} - : 0; - $mycalc{'server_buffers'} += - ( defined $myvar{'innodb_log_buffer_size'} ) - ? $myvar{'innodb_log_buffer_size'} - : 0; - $mycalc{'server_buffers'} += - ( defined $myvar{'query_cache_size'} ) ? $myvar{'query_cache_size'} : 0; - $mycalc{'server_buffers'} += - ( defined $myvar{'aria_pagecache_buffer_size'} ) - ? $myvar{'aria_pagecache_buffer_size'} - : 0; - - # Global memory - # Max used memory is memory used by MySQL based on Max_used_connections - # This is the max memory used theoretically calculated with the max concurrent connection number reached by mysql - $mycalc{'max_used_memory'} = - $mycalc{'server_buffers'} + - $mycalc{"max_total_per_thread_buffers"} + - get_pf_memory(); - - # + get_gcache_memory(); - $mycalc{'pct_max_used_memory'} = - percentage( $mycalc{'max_used_memory'}, $physical_memory ); - - # Total possible memory is memory needed by MySQL based on max_connections - # This is the max memory MySQL can theoretically used if all connections allowed has opened by mysql - $mycalc{'max_peak_memory'} = - $mycalc{'server_buffers'} + - $mycalc{'total_per_thread_buffers'} + - get_pf_memory(); - - # + get_gcache_memory(); - $mycalc{'pct_max_physical_memory'} = - percentage( $mycalc{'max_peak_memory'}, $physical_memory ); - - debugprint "Max Used Memory: " - . hr_bytes( $mycalc{'max_used_memory'} ) . ""; - debugprint "Max Used Percentage RAM: " - . $mycalc{'pct_max_used_memory'} . "%"; - - debugprint "Max Peak Memory: " - . hr_bytes( $mycalc{'max_peak_memory'} ) . ""; - debugprint "Max Peak Percentage RAM: " - . $mycalc{'pct_max_physical_memory'} . "%"; - - # Slow queries - $mycalc{'pct_slow_queries'} = - int( ( $mystat{'Slow_queries'} / $mystat{'Questions'} ) * 100 ); - - # Connections - $mycalc{'pct_connections_used'} = int( - ( $mystat{'Max_used_connections'} / $myvar{'max_connections'} ) * 100 ); - $mycalc{'pct_connections_used'} = - ( $mycalc{'pct_connections_used'} > 100 ) - ? 100 - : $mycalc{'pct_connections_used'}; - - # Aborted Connections - $mycalc{'pct_connections_aborted'} = - percentage( $mystat{'Aborted_connects'}, $mystat{'Connections'} ); - debugprint "Aborted_connects: " . $mystat{'Aborted_connects'} . ""; - debugprint "Connections: " . $mystat{'Connections'} . ""; - debugprint "pct_connections_aborted: " - . $mycalc{'pct_connections_aborted'} . ""; - - # Key buffers - if ( mysql_version_ge( 4, 1 ) && $myvar{'key_buffer_size'} > 0 ) { - $mycalc{'pct_key_buffer_used'} = sprintf( - "%.1f", - ( - 1 - ( - ( - $mystat{'Key_blocks_unused'} * - $myvar{'key_cache_block_size'} - ) / $myvar{'key_buffer_size'} - ) - ) * 100 - ); - } - else { - $mycalc{'pct_key_buffer_used'} = 0; - } - - if ( $mystat{'Key_read_requests'} > 0 ) { - $mycalc{'pct_keys_from_mem'} = sprintf( - "%.1f", - ( - 100 - ( - ( $mystat{'Key_reads'} / $mystat{'Key_read_requests'} ) * - 100 - ) - ) - ); - } - else { - $mycalc{'pct_keys_from_mem'} = 0; - } - if ( defined $mystat{'Aria_pagecache_read_requests'} - && $mystat{'Aria_pagecache_read_requests'} > 0 ) - { - $mycalc{'pct_aria_keys_from_mem'} = sprintf( - "%.1f", - ( - 100 - ( - ( - $mystat{'Aria_pagecache_reads'} / - $mystat{'Aria_pagecache_read_requests'} - ) * 100 - ) - ) - ); - } - else { - $mycalc{'pct_aria_keys_from_mem'} = 0; - } - - if ( $mystat{'Key_write_requests'} > 0 ) { - $mycalc{'pct_wkeys_from_mem'} = sprintf( "%.1f", - ( ( $mystat{'Key_writes'} / $mystat{'Key_write_requests'} ) * 100 ) - ); - } - else { - $mycalc{'pct_wkeys_from_mem'} = 0; - } - - if ( $doremote eq 0 and !mysql_version_ge(5) ) { - my $size = 0; - $size += (split)[0] - for - `find "$myvar{'datadir'}" -name "*.MYI" -print0 2>&1 | xargs $xargsflags -0 du -L $duflags 2>&1`; - $mycalc{'total_myisam_indexes'} = $size; - $size = 0 + (split)[0] - for - `find "$myvar{'datadir'}" -name "*.MAI" -print0 2>&1 | xargs $xargsflags -0 du -L $duflags 2>&1`; - $mycalc{'total_aria_indexes'} = $size; - } - elsif ( mysql_version_ge(5) ) { - $mycalc{'total_myisam_indexes'} = select_one - "SELECT IFNULL(SUM(INDEX_LENGTH), 0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'MyISAM';"; - $mycalc{'total_aria_indexes'} = select_one - "SELECT IFNULL(SUM(INDEX_LENGTH), 0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'Aria';"; - } - if ( defined $mycalc{'total_myisam_indexes'} ) { - chomp( $mycalc{'total_myisam_indexes'} ); - } - if ( defined $mycalc{'total_aria_indexes'} ) { - chomp( $mycalc{'total_aria_indexes'} ); - } - - # Query cache - if ( mysql_version_ge(8) and mysql_version_le(10) ) { - $mycalc{'query_cache_efficiency'} = 0; - } - elsif ( mysql_version_ge(4) ) { - $mycalc{'query_cache_efficiency'} = sprintf( - "%.1f", - ( - $mystat{'Qcache_hits'} / - ( $mystat{'Com_select'} + $mystat{'Qcache_hits'} ) - ) * 100 - ); - if ( $myvar{'query_cache_size'} ) { - $mycalc{'pct_query_cache_used'} = sprintf( - "%.1f", - 100 - ( - $mystat{'Qcache_free_memory'} / $myvar{'query_cache_size'} - ) * 100 - ); - } - if ( $mystat{'Qcache_lowmem_prunes'} == 0 ) { - $mycalc{'query_cache_prunes_per_day'} = 0; - } - else { - $mycalc{'query_cache_prunes_per_day'} = int( - $mystat{'Qcache_lowmem_prunes'} / ( $mystat{'Uptime'} / 86400 ) - ); - } - } - - # Sorting - $mycalc{'total_sorts'} = $mystat{'Sort_scan'} + $mystat{'Sort_range'}; - if ( $mycalc{'total_sorts'} > 0 ) { - $mycalc{'pct_temp_sort_table'} = int( - ( $mystat{'Sort_merge_passes'} / $mycalc{'total_sorts'} ) * 100 ); - } - - # Joins - $mycalc{'joins_without_indexes'} = - $mystat{'Select_range_check'} + $mystat{'Select_full_join'}; - $mycalc{'joins_without_indexes_per_day'} = - int( $mycalc{'joins_without_indexes'} / ( $mystat{'Uptime'} / 86400 ) ); - - # Temporary tables - if ( $mystat{'Created_tmp_tables'} > 0 ) { - if ( $mystat{'Created_tmp_disk_tables'} > 0 ) { - $mycalc{'pct_temp_disk'} = int( - ( - $mystat{'Created_tmp_disk_tables'} / - $mystat{'Created_tmp_tables'} - ) * 100 - ); - } - else { - $mycalc{'pct_temp_disk'} = 0; - } - } - - # Table cache - if ( $mystat{'Opened_tables'} > 0 ) { - if ( not defined( $mystat{'Table_open_cache_hits'} ) ) { - $mycalc{'table_cache_hit_rate'} = - int( $mystat{'Open_tables'} * 100 / $mystat{'Opened_tables'} ); - } - else { - $mycalc{'table_cache_hit_rate'} = int( - $mystat{'Table_open_cache_hits'} * 100 / ( - $mystat{'Table_open_cache_hits'} + - $mystat{'Table_open_cache_misses'} - ) - ); - } - } - else { - $mycalc{'table_cache_hit_rate'} = 100; - } - - # Open files - if ( $myvar{'open_files_limit'} > 0 ) { - $mycalc{'pct_files_open'} = - int( $mystat{'Open_files'} * 100 / $myvar{'open_files_limit'} ); - } - - # Table locks - if ( $mystat{'Table_locks_immediate'} > 0 ) { - if ( $mystat{'Table_locks_waited'} == 0 ) { - $mycalc{'pct_table_locks_immediate'} = 100; - } - else { - $mycalc{'pct_table_locks_immediate'} = int( - $mystat{'Table_locks_immediate'} * 100 / ( - $mystat{'Table_locks_waited'} + - $mystat{'Table_locks_immediate'} - ) - ); - } - } - - # Thread cache - $mycalc{'thread_cache_hit_rate'} = - int( 100 - - ( ( $mystat{'Threads_created'} / $mystat{'Connections'} ) * 100 ) ); - - # Other - if ( $mystat{'Connections'} > 0 ) { - $mycalc{'pct_aborted_connections'} = - int( ( $mystat{'Aborted_connects'} / $mystat{'Connections'} ) * 100 ); - } - if ( $mystat{'Questions'} > 0 ) { - $mycalc{'total_reads'} = $mystat{'Com_select'}; - $mycalc{'total_writes'} = - $mystat{'Com_delete'} + - $mystat{'Com_insert'} + - $mystat{'Com_update'} + - $mystat{'Com_replace'}; - if ( $mycalc{'total_reads'} == 0 ) { - $mycalc{'pct_reads'} = 0; - $mycalc{'pct_writes'} = 100; - } - else { - $mycalc{'pct_reads'} = int( - ( - $mycalc{'total_reads'} / - ( $mycalc{'total_reads'} + $mycalc{'total_writes'} ) - ) * 100 - ); - $mycalc{'pct_writes'} = 100 - $mycalc{'pct_reads'}; - } - } - - # InnoDB - $myvar{'innodb_log_files_in_group'} = 1 - unless defined( $myvar{'innodb_log_files_in_group'} ); - $myvar{'innodb_log_files_in_group'} = 1 - if $myvar{'innodb_log_files_in_group'} == 0; - - $myvar{"innodb_buffer_pool_instances"} = 1 - unless defined( $myvar{'innodb_buffer_pool_instances'} ); - if ( $myvar{'have_innodb'} eq "YES" ) { - if ( defined $myvar{'innodb_redo_log_capacity'} ) { - $mycalc{'innodb_log_size_pct'} = - ( $myvar{'innodb_redo_log_capacity'} / - $myvar{'innodb_buffer_pool_size'} ) * 100; - } else { - $mycalc{'innodb_log_size_pct'} = - ( $myvar{'innodb_log_file_size'} * - $myvar{'innodb_log_files_in_group'} * 100 / - $myvar{'innodb_buffer_pool_size'} ); - } - } - if ( !defined $myvar{'innodb_buffer_pool_size'} ) { - $mycalc{'innodb_log_size_pct'} = 0; - $myvar{'innodb_buffer_pool_size'} = 0; - } - - # InnoDB Buffer pool read cache efficiency - ( - $mystat{'Innodb_buffer_pool_read_requests'}, - $mystat{'Innodb_buffer_pool_reads'} - ) - = ( 1, 1 ) - unless defined $mystat{'Innodb_buffer_pool_reads'}; - $mycalc{'pct_read_efficiency'} = percentage( - $mystat{'Innodb_buffer_pool_read_requests'}, - ( - $mystat{'Innodb_buffer_pool_read_requests'} + - $mystat{'Innodb_buffer_pool_reads'} - ) - ) if defined $mystat{'Innodb_buffer_pool_read_requests'}; - debugprint "pct_read_efficiency: " . $mycalc{'pct_read_efficiency'} . ""; - debugprint "Innodb_buffer_pool_reads: " - . $mystat{'Innodb_buffer_pool_reads'} . ""; - debugprint "Innodb_buffer_pool_read_requests: " - . $mystat{'Innodb_buffer_pool_read_requests'} . ""; - - # InnoDB log write cache efficiency - ( $mystat{'Innodb_log_write_requests'}, $mystat{'Innodb_log_writes'} ) = - ( 1, 1 ) - unless defined $mystat{'Innodb_log_writes'}; - $mycalc{'pct_write_efficiency'} = percentage( - ( $mystat{'Innodb_log_write_requests'} - $mystat{'Innodb_log_writes'} ), - $mystat{'Innodb_log_write_requests'} - ) if defined $mystat{'Innodb_log_write_requests'}; - debugprint "pct_write_efficiency: " . $mycalc{'pct_write_efficiency'} . ""; - debugprint "Innodb_log_writes: " . $mystat{'Innodb_log_writes'} . ""; - debugprint "Innodb_log_write_requests: " - . $mystat{'Innodb_log_write_requests'} . ""; - $mycalc{'pct_innodb_buffer_used'} = percentage( - ( - $mystat{'Innodb_buffer_pool_pages_total'} - - $mystat{'Innodb_buffer_pool_pages_free'} - ), - $mystat{'Innodb_buffer_pool_pages_total'} - ) if defined $mystat{'Innodb_buffer_pool_pages_total'}; - - my $lreq = - "select ROUND( 100* sum(allocated)/ " - . $myvar{'innodb_buffer_pool_size'} - . ',1) FROM sys.x\$innodb_buffer_stats_by_table;'; - debugprint("lreq: $lreq"); - $mycalc{'innodb_buffer_alloc_pct'} = select_one($lreq) - if ( $opt{experimental} ); - - # Binlog Cache - if ( $myvar{'log_bin'} ne 'OFF' ) { - $mycalc{'pct_binlog_cache'} = percentage( - $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'}, - $mystat{'Binlog_cache_use'} ); - } -} - -sub mysql_stats { - subheaderprint "Performance Metrics"; - - # Show uptime, queries per second, connections, traffic stats - my $qps; - if ( $mystat{'Uptime'} > 0 ) { - $qps = sprintf( "%.3f", $mystat{'Questions'} / $mystat{'Uptime'} ); - } - push( @generalrec, - "MySQL was started within the last 24 hours: recommendations may be inaccurate" - ) if ( $mystat{'Uptime'} < 86400 ); - infoprint "Up for: " - . pretty_uptime( $mystat{'Uptime'} ) . " (" - . hr_num( $mystat{'Questions'} ) . " q [" - . hr_num($qps) - . " qps], " - . hr_num( $mystat{'Connections'} ) - . " conn," . " TX: " - . hr_bytes_rnd( $mystat{'Bytes_sent'} ) - . ", RX: " - . hr_bytes_rnd( $mystat{'Bytes_received'} ) . ")"; - infoprint "Reads / Writes: " - . $mycalc{'pct_reads'} . "% / " - . $mycalc{'pct_writes'} . "%"; - - # Binlog Cache - if ( $myvar{'log_bin'} eq 'OFF' ) { - infoprint "Binary logging is disabled"; - } - else { - infoprint "Binary logging is enabled (GTID MODE: " - . ( defined( $myvar{'gtid_mode'} ) ? $myvar{'gtid_mode'} : "OFF" ) - . ")"; - } - - # Memory usage - infoprint "Physical Memory : " . hr_bytes($physical_memory); - infoprint "Max MySQL memory : " . hr_bytes( $mycalc{'max_peak_memory'} ); - infoprint "Other process memory: " . hr_bytes( get_other_process_memory() ); - - infoprint "Total buffers: " - . hr_bytes( $mycalc{'server_buffers'} ) - . " global + " - . hr_bytes( $mycalc{'per_thread_buffers'} ) - . " per thread ($myvar{'max_connections'} max threads)"; - infoprint "Performance_schema Max memory usage: " - . hr_bytes_rnd( get_pf_memory() ); - $result{'Performance_schema'}{'memory'} = get_pf_memory(); - $result{'Performance_schema'}{'pretty_memory'} = - hr_bytes_rnd( get_pf_memory() ); - infoprint "Galera GCache Max memory usage: " - . hr_bytes_rnd( get_gcache_memory() ); - $result{'Galera'}{'GCache'}{'memory'} = get_gcache_memory(); - $result{'Galera'}{'GCache'}{'pretty_memory'} = - hr_bytes_rnd( get_gcache_memory() ); - - if ( $opt{buffers} ne 0 ) { - infoprint "Global Buffers"; - infoprint " +-- Key Buffer: " - . hr_bytes( $myvar{'key_buffer_size'} ) . ""; - infoprint " +-- Max Tmp Table: " - . hr_bytes( $mycalc{'max_tmp_table_size'} ) . ""; - - if ( defined $myvar{'query_cache_type'} ) { - infoprint "Query Cache Buffers"; - infoprint " +-- Query Cache: " - . $myvar{'query_cache_type'} . " - " - . ( - $myvar{'query_cache_type'} eq 0 | - $myvar{'query_cache_type'} eq 'OFF' ? "DISABLED" - : ( - $myvar{'query_cache_type'} eq 1 ? "ALL REQUESTS" - : "ON DEMAND" - ) - ) . ""; - infoprint " +-- Query Cache Size: " - . hr_bytes( $myvar{'query_cache_size'} ) . ""; - } - - infoprint "Per Thread Buffers"; - infoprint " +-- Read Buffer: " - . hr_bytes( $myvar{'read_buffer_size'} ) . ""; - infoprint " +-- Read RND Buffer: " - . hr_bytes( $myvar{'read_rnd_buffer_size'} ) . ""; - infoprint " +-- Sort Buffer: " - . hr_bytes( $myvar{'sort_buffer_size'} ) . ""; - infoprint " +-- Thread stack: " - . hr_bytes( $myvar{'thread_stack'} ) . ""; - infoprint " +-- Join Buffer: " - . hr_bytes( $myvar{'join_buffer_size'} ) . ""; - if ( $myvar{'log_bin'} ne 'OFF' ) { - infoprint "Binlog Cache Buffers"; - infoprint " +-- Binlog Cache: " - . hr_bytes( $myvar{'binlog_cache_size'} ) . ""; - } - } - - if ( $arch - && $arch == 32 - && $mycalc{'max_used_memory'} > 2 * 1024 * 1024 * 1024 ) - { - badprint - "Allocating > 2GB RAM on 32-bit systems can cause system instability"; - badprint "Maximum reached memory usage: " - . hr_bytes( $mycalc{'max_used_memory'} ) - . " ($mycalc{'pct_max_used_memory'}% of installed RAM)"; - } - elsif ( $mycalc{'pct_max_used_memory'} > 85 ) { - badprint "Maximum reached memory usage: " - . hr_bytes( $mycalc{'max_used_memory'} ) - . " ($mycalc{'pct_max_used_memory'}% of installed RAM)"; - } - else { - goodprint "Maximum reached memory usage: " - . hr_bytes( $mycalc{'max_used_memory'} ) - . " ($mycalc{'pct_max_used_memory'}% of installed RAM)"; - } - - if ( $mycalc{'pct_max_physical_memory'} > 85 ) { - badprint "Maximum possible memory usage: " - . hr_bytes( $mycalc{'max_peak_memory'} ) - . " ($mycalc{'pct_max_physical_memory'}% of installed RAM)"; - push( @generalrec, - "Reduce your overall MySQL memory footprint for system stability" ); - } - else { - goodprint "Maximum possible memory usage: " - . hr_bytes( $mycalc{'max_peak_memory'} ) - . " ($mycalc{'pct_max_physical_memory'}% of installed RAM)"; - } - - if ( $physical_memory < - ( $mycalc{'max_peak_memory'} + get_other_process_memory() ) ) - { - if ( $opt{nondedicated} ) { - infoprint "No warning with --nondedicated option"; - infoprint - "Overall possible memory usage with other process exceeded memory"; - } - else { - badprint - "Overall possible memory usage with other process exceeded memory"; - push( @generalrec, - "Dedicate this server to your database for highest performance." - ); - } - } - else { - goodprint - "Overall possible memory usage with other process is compatible with memory available"; - } - - # Slow queries - if ( $mycalc{'pct_slow_queries'} > 5 ) { - badprint "Slow queries: $mycalc{'pct_slow_queries'}% (" - . hr_num( $mystat{'Slow_queries'} ) . "/" - . hr_num( $mystat{'Questions'} ) . ")"; - } - else { - goodprint "Slow queries: $mycalc{'pct_slow_queries'}% (" - . hr_num( $mystat{'Slow_queries'} ) . "/" - . hr_num( $mystat{'Questions'} ) . ")"; - } - if ( $myvar{'long_query_time'} > 10 ) { - push( @adjvars, "long_query_time (<= 10)" ); - } - if ( defined( $myvar{'log_slow_queries'} ) ) { - if ( $myvar{'log_slow_queries'} eq "OFF" ) { - push( @generalrec, - "Enable the slow query log to troubleshoot bad queries" ); - } - } - - # Connections - if ( $mycalc{'pct_connections_used'} > 85 ) { - badprint - "Highest connection usage: $mycalc{'pct_connections_used'}% ($mystat{'Max_used_connections'}/$myvar{'max_connections'})"; - push( @adjvars, - "max_connections (> " . $myvar{'max_connections'} . ")" ); - push( @adjvars, - "wait_timeout (< " . $myvar{'wait_timeout'} . ")", - "interactive_timeout (< " . $myvar{'interactive_timeout'} . ")" ); - push( @generalrec, - "Reduce or eliminate persistent connections to reduce connection usage" - ); - } - else { - goodprint - "Highest usage of available connections: $mycalc{'pct_connections_used'}% ($mystat{'Max_used_connections'}/$myvar{'max_connections'})"; - } - - # Aborted Connections - if ( $mycalc{'pct_connections_aborted'} > 3 ) { - badprint - "Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})"; - push( @generalrec, - "Reduce or eliminate unclosed connections and network issues" ); - } - else { - goodprint - "Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})"; - } - - # name resolution - debugprint "skip name resolve: $result{'Variables'}{'skip_name_resolve'}" - if ( defined( $result{'Variables'}{'skip_name_resolve'} ) ); - if ( defined( $result{'Variables'}{'skip_networking'} ) - && $result{'Variables'}{'skip_networking'} eq 'ON' ) - { - infoprint - "Skipped name resolution test due to skip_networking=ON in system variables."; - } - elsif ( not defined( $result{'Variables'}{'skip_name_resolve'} ) ) { - infoprint - "Skipped name resolution test due to missing skip_name_resolve in system variables."; - } - - #Cpanel and Skip name resolve - elsif ( -r "/usr/local/cpanel/cpanel" ) { - if ( $result{'Variables'}{'skip_name_resolve'} ne 'OFF' ) { - infoprint "CPanel and Flex system skip-name-resolve should be on"; - } - if ( $result{'Variables'}{'skip_name_resolve'} eq 'OFF' ) { - badprint "CPanel and Flex system skip-name-resolve should be on"; - push( @generalrec, - "name resolution is enabled due to cPanel doesn't support this disabled." - ); - push( @adjvars, "skip-name-resolve=0" ); - } - } - elsif ( $result{'Variables'}{'skip_name_resolve'} ne 'ON' - and $result{'Variables'}{'skip_name_resolve'} ne '1' ) - { - badprint - "Name resolution is active: a reverse name resolution is made for each new connection which can reduce performance"; - push( @generalrec, - "Configure your accounts with ip or subnets only, then update your configuration with skip-name-resolve=ON" - ); - push( @adjvars, "skip-name-resolve=ON" ); - } - - # Query cache - if ( !mysql_version_ge(4) ) { - - # MySQL versions < 4.01 don't support query caching - push( @generalrec, - "Upgrade MySQL to version 4+ to utilize query caching" ); - } - elsif ( mysql_version_ge(8) and mysql_version_le(9,9) ) { - infoprint "Query cache has been removed since MySQL 8.0"; - - #return; - } - elsif ($myvar{'query_cache_size'} < 1 - or $myvar{'query_cache_type'} eq "OFF" ) - { - goodprint - "Query cache is disabled by default due to mutex contention on multiprocessor machines."; - } - elsif ( $mystat{'Com_select'} == 0 ) { - badprint - "Query cache cannot be analyzed: no SELECT statements executed"; - } - else { - if ( $mycalc{'query_cache_efficiency'} < 20 ) { - badprint - "Query cache efficiency: $mycalc{'query_cache_efficiency'}% (" - . hr_num( $mystat{'Qcache_hits'} ) - . " cached / " - . hr_num( $mystat{'Qcache_hits'} + $mystat{'Com_select'} ) - . " selects)"; - push( @adjvars, - "query_cache_limit (> " - . hr_bytes_rnd( $myvar{'query_cache_limit'} ) - . ", or use smaller result sets)" ); - badprint - "Query cache may be disabled by default due to mutex contention."; - push( @adjvars, "query_cache_size (=0)" ); - push( @adjvars, "query_cache_type (=0)" ); - } - else { - goodprint - "Query cache efficiency: $mycalc{'query_cache_efficiency'}% (" - . hr_num( $mystat{'Qcache_hits'} ) - . " cached / " - . hr_num( $mystat{'Qcache_hits'} + $mystat{'Com_select'} ) - . " selects)"; - if ( $mycalc{'query_cache_prunes_per_day'} > 98 ) { - badprint - "Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}"; - if ( $myvar{'query_cache_size'} >= 128 * 1024 * 1024 ) { - push( @generalrec, - "Increasing the query_cache size over 128M may reduce performance" - ); - push( @adjvars, - "query_cache_size (> " - . hr_bytes_rnd( $myvar{'query_cache_size'} ) - . ") [see warning above]" ); - } - else { - push( @adjvars, - "query_cache_size (> " - . hr_bytes_rnd( $myvar{'query_cache_size'} ) - . ")" ); - } - } - else { - goodprint - "Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}"; - } - } - - } - - # Sorting - if ( $mycalc{'total_sorts'} == 0 ) { - goodprint "No Sort requiring temporary tables"; - } - elsif ( $mycalc{'pct_temp_sort_table'} > 10 ) { - badprint - "Sorts requiring temporary tables: $mycalc{'pct_temp_sort_table'}% (" - . hr_num( $mystat{'Sort_merge_passes'} ) - . " temp sorts / " - . hr_num( $mycalc{'total_sorts'} ) - . " sorts)"; - push( @adjvars, - "sort_buffer_size (> " - . hr_bytes_rnd( $myvar{'sort_buffer_size'} ) - . ")" ); - push( @adjvars, - "read_rnd_buffer_size (> " - . hr_bytes_rnd( $myvar{'read_rnd_buffer_size'} ) - . ")" ); - } - else { - goodprint - "Sorts requiring temporary tables: $mycalc{'pct_temp_sort_table'}% (" - . hr_num( $mystat{'Sort_merge_passes'} ) - . " temp sorts / " - . hr_num( $mycalc{'total_sorts'} ) - . " sorts)"; - } - - # Joins - if ( $mycalc{'joins_without_indexes_per_day'} > 250 ) { - badprint - "Joins performed without indexes: $mycalc{'joins_without_indexes'}"; - push( @adjvars, - "join_buffer_size (> " - . hr_bytes( $myvar{'join_buffer_size'} ) - . ", or always use indexes with JOINs)" ); - push( - @generalrec, - "We will suggest raising the 'join_buffer_size' until JOINs not using indexes are found. - See https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_join_buffer_size" - ); - } - else { - goodprint "No joins without indexes"; - - # No joins have run without indexes - } - - # Temporary tables - if ( $mystat{'Created_tmp_tables'} > 0 ) { - if ( $mycalc{'pct_temp_disk'} > 25 - && $mycalc{'max_tmp_table_size'} < 256 * 1024 * 1024 ) - { - badprint - "Temporary tables created on disk: $mycalc{'pct_temp_disk'}% (" - . hr_num( $mystat{'Created_tmp_disk_tables'} ) - . " on disk / " - . hr_num( $mystat{'Created_tmp_tables'} ) - . " total)"; - push( @adjvars, - "tmp_table_size (> " - . hr_bytes_rnd( $myvar{'tmp_table_size'} ) - . ")" ); - push( @adjvars, - "max_heap_table_size (> " - . hr_bytes_rnd( $myvar{'max_heap_table_size'} ) - . ")" ); - push( @generalrec, - "When making adjustments, make tmp_table_size/max_heap_table_size equal" - ); - push( @generalrec, - "Reduce your SELECT DISTINCT queries which have no LIMIT clause" - ); - } - elsif ($mycalc{'pct_temp_disk'} > 25 - && $mycalc{'max_tmp_table_size'} >= 256 * 1024 * 1024 ) - { - badprint - "Temporary tables created on disk: $mycalc{'pct_temp_disk'}% (" - . hr_num( $mystat{'Created_tmp_disk_tables'} ) - . " on disk / " - . hr_num( $mystat{'Created_tmp_tables'} ) - . " total)"; - push( @generalrec, - "Temporary table size is already large: reduce result set size" - ); - push( @generalrec, - "Reduce your SELECT DISTINCT queries without LIMIT clauses" ); - } - else { - goodprint - "Temporary tables created on disk: $mycalc{'pct_temp_disk'}% (" - . hr_num( $mystat{'Created_tmp_disk_tables'} ) - . " on disk / " - . hr_num( $mystat{'Created_tmp_tables'} ) - . " total)"; - } - } - else { - goodprint "No tmp tables created on disk"; - } - - # Thread cache - if ( defined( $myvar{'have_threadpool'} ) - and $myvar{'have_threadpool'} eq 'YES' ) - { - # https://www.percona.com/doc/percona-server/5.7/performance/threadpool.html#status-variables - # When thread pool is enabled, the value of the thread_cache_size variable - # is ignored. The Threads_cached status variable contains 0 in this case. - infoprint "Thread cache not used with thread pool enabled"; - } - else { - if ( $myvar{'thread_cache_size'} eq 0 ) { - badprint "Thread cache is disabled"; - push( @generalrec, - "Set thread_cache_size to 4 as a starting value" ); - push( @adjvars, "thread_cache_size (start at 4)" ); - } - else { - if ( $mycalc{'thread_cache_hit_rate'} <= 50 ) { - badprint - "Thread cache hit rate: $mycalc{'thread_cache_hit_rate'}% (" - . hr_num( $mystat{'Threads_created'} ) - . " created / " - . hr_num( $mystat{'Connections'} ) - . " connections)"; - push( @adjvars, - "thread_cache_size (> $myvar{'thread_cache_size'})" ); - } - else { - goodprint - "Thread cache hit rate: $mycalc{'thread_cache_hit_rate'}% (" - . hr_num( $mystat{'Threads_created'} ) - . " created / " - . hr_num( $mystat{'Connections'} ) - . " connections)"; - } - } - } - - # Table cache - my $table_cache_var = ""; - if ( $mystat{'Open_tables'} > 0 ) { - if ( $mycalc{'table_cache_hit_rate'} < 20 ) { - - unless ( defined( $mystat{'Table_open_cache_hits'} ) ) { - badprint - "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" - . hr_num( $mystat{'Open_tables'} ) - . " hits / " - . hr_num( $mystat{'Opened_tables'} ) - . " requests)"; - } - else { - badprint - "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" - . hr_num( $mystat{'Table_open_cache_hits'} ) - . " hits / " - . hr_num( $mystat{'Table_open_cache_hits'} + - $mystat{'Table_open_cache_misses'} ) - . " requests)"; - } - - if ( mysql_version_ge( 5, 1 ) ) { - $table_cache_var = "table_open_cache"; - } - else { - $table_cache_var = "table_cache"; - } - - push( @adjvars, - $table_cache_var . " (> " . $myvar{$table_cache_var} . ")" ); - push( @generalrec, - "Increase " - . $table_cache_var - . " gradually to avoid file descriptor limits" ); - push( @generalrec, - "Read this before increasing " - . $table_cache_var - . " over 64: https://bit.ly/2Fulv7r" ); - push( @generalrec, - "Read this before increasing for MariaDB" - . " https://mariadb.com/kb/en/library/optimizing-table_open_cache/" - ); - push( @generalrec, - "This is MyISAM only table_cache scalability problem, InnoDB not affected." - ); - push( @generalrec, - "For more details see: https://bugs.mysql.com/bug.php?id=49177" - ); - push( @generalrec, - "This bug already fixed in MySQL 5.7.9 and newer MySQL versions." - ); - push( @generalrec, - "Beware that open_files_limit (" - . $myvar{'open_files_limit'} - . ") variable " ); - push( @generalrec, - "should be greater than $table_cache_var (" - . $myvar{$table_cache_var} - . ")" ); - } - else { - unless ( defined( $mystat{'Table_open_cache_hits'} ) ) { - goodprint - "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" - . hr_num( $mystat{'Open_tables'} ) - . " hits / " - . hr_num( $mystat{'Opened_tables'} ) - . " requests)"; - } - else { - goodprint - "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" - . hr_num( $mystat{'Table_open_cache_hits'} ) - . " hits / " - . hr_num( $mystat{'Table_open_cache_hits'} + - $mystat{'Table_open_cache_misses'} ) - . " requests)"; - } - } - } - - # Table definition cache - my $nbtables = select_one('SELECT COUNT(*) FROM information_schema.tables'); - $mycalc{'total_tables'} = $nbtables; - if ( defined $myvar{'table_definition_cache'} ) { - if ( $myvar{'table_definition_cache'} == -1 ) { - infoprint( "table_definition_cache (" - . $myvar{'table_definition_cache'} - . ") is in autosizing mode" ); - } - elsif ( $myvar{'table_definition_cache'} < $nbtables ) { - badprint "table_definition_cache (" - . $myvar{'table_definition_cache'} - . ") is less than number of tables ($nbtables) "; - push( @adjvars, - "table_definition_cache (" - . $myvar{'table_definition_cache'} . ") > " - . $nbtables - . " or -1 (autosizing if supported)" ); - } - else { - goodprint "table_definition_cache (" - . $myvar{'table_definition_cache'} - . ") is greater than number of tables ($nbtables)"; - } - } - else { - infoprint "No table_definition_cache variable found."; - } - - # Open files - if ( defined $mycalc{'pct_files_open'} ) { - if ( $mycalc{'pct_files_open'} > 85 ) { - badprint "Open file limit used: $mycalc{'pct_files_open'}% (" - . hr_num( $mystat{'Open_files'} ) . "/" - . hr_num( $myvar{'open_files_limit'} ) . ")"; - push( @adjvars, - "open_files_limit (> " . $myvar{'open_files_limit'} . ")" ); - } - else { - goodprint "Open file limit used: $mycalc{'pct_files_open'}% (" - . hr_num( $mystat{'Open_files'} ) . "/" - . hr_num( $myvar{'open_files_limit'} ) . ")"; - } - } - - # Table locks - if ( defined $mycalc{'pct_table_locks_immediate'} ) { - if ( $mycalc{'pct_table_locks_immediate'} < 95 ) { - badprint - "Table locks acquired immediately: $mycalc{'pct_table_locks_immediate'}%"; - push( @generalrec, - "Optimize queries and/or use InnoDB to reduce lock wait" ); - } - else { - goodprint - "Table locks acquired immediately: $mycalc{'pct_table_locks_immediate'}% (" - . hr_num( $mystat{'Table_locks_immediate'} ) - . " immediate / " - . hr_num( $mystat{'Table_locks_waited'} + - $mystat{'Table_locks_immediate'} ) - . " locks)"; - } - } - - # Binlog cache - if ( defined $mycalc{'pct_binlog_cache'} ) { - if ( $mycalc{'pct_binlog_cache'} < 90 - && $mystat{'Binlog_cache_use'} > 0 ) - { - badprint "Binlog cache memory access: " - . $mycalc{'pct_binlog_cache'} . "% (" - . ( - $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'} ) - . " Memory / " - . $mystat{'Binlog_cache_use'} - . " Total)"; - push( @generalrec, - "Increase binlog_cache_size (current value: " - . $myvar{'binlog_cache_size'} - . ")" ); - push( @adjvars, - "binlog_cache_size (" - . hr_bytes( $myvar{'binlog_cache_size'} + 16 * 1024 * 1024 ) - . ")" ); - } - else { - goodprint "Binlog cache memory access: " - . $mycalc{'pct_binlog_cache'} . "% (" - . ( - $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'} ) - . " Memory / " - . $mystat{'Binlog_cache_use'} - . " Total)"; - debugprint "Not enough data to validate binlog cache size\n" - if $mystat{'Binlog_cache_use'} < 10; - } - } - - # Performance options - if ( !mysql_version_ge( 5, 1 ) ) { - push( @generalrec, "Upgrade to MySQL 5.5+ to use asynchronous write" ); - } - elsif ( $myvar{'concurrent_insert'} eq "OFF" ) { - push( @generalrec, "Enable concurrent_insert by setting it to 'ON'" ); - } - elsif ( $myvar{'concurrent_insert'} eq 0 ) { - push( @generalrec, "Enable concurrent_insert by setting it to 1" ); - } -} - -# Recommendations for MyISAM -sub mysql_myisam { - return 0 unless ( $opt{'myisamstat'} > 0 ); - subheaderprint "MyISAM Metrics"; - my $nb_myisam_tables = select_one( - "SELECT COUNT(*) FROM information_schema.TABLES WHERE ENGINE='MyISAM' and TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema')" - ); - push( @generalrec, - "MyISAM engine is deprecated, consider migrating to InnoDB" ) - if $nb_myisam_tables > 0; - - if ( $nb_myisam_tables > 0 ) { - badprint - "Consider migrating $nb_myisam_tables following tables to InnoDB:"; - my $sql_mig = ""; - for my $myisam_table ( - select_array( - "SELECT CONCAT('|',TABLE_SCHEMA, '|.|', TABLE_NAME,'|') FROM information_schema.TABLES WHERE ENGINE='MyISAM' and TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema')" - ) - ) - { - my $myisam_table_escape = $myisam_table =~ s/\|/\`/gr; - $sql_mig = - "${sql_mig}-- InnoDB migration for $myisam_table_escape\nALTER TABLE $myisam_table_escape ENGINE=InnoDB;\n\n"; - infoprint - "* InnoDB migration request for $myisam_table_escape Table: ALTER TABLE $myisam_table_escape ENGINE=InnoDB;"; - } - dump_into_file( "migrate_myisam_to_innodb.sql", $sql_mig ); - } - infoprint("General MyIsam metrics:"); - infoprint " +-- Total MyISAM Tables : $nb_myisam_tables"; - infoprint " +-- Total MyISAM indexes : " - . hr_bytes( $mycalc{'total_myisam_indexes'} ) - if defined( $mycalc{'total_myisam_indexes'} ); - infoprint " +-- KB Size :" . hr_bytes( $myvar{'key_buffer_size'} ); - infoprint " +-- KB Used Size :" - . hr_bytes( $myvar{'key_buffer_size'} - - $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ); - infoprint " +-- KB used :" . $mycalc{'pct_key_buffer_used'} . "%"; - infoprint " +-- Read KB hit rate: $mycalc{'pct_keys_from_mem'}% (" - . hr_num( $mystat{'Key_read_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_reads'} ) - . " reads)"; - infoprint " +-- Write KB hit rate: $mycalc{'pct_wkeys_from_mem'}% (" - . hr_num( $mystat{'Key_write_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_writes'} ) - . " writes)"; - - if ( $nb_myisam_tables == 0 ) { - infoprint "No MyISAM table(s) detected ...."; - return; - } - if ( mysql_version_ge(8) and mysql_version_le(10) ) { - infoprint "MyISAM Metrics are disabled since MySQL 8.0."; - if ( $myvar{'key_buffer_size'} > 0 ) { - push( @adjvars, "key_buffer_size=0" ); - push( @generalrec, - "Buffer Key MyISAM set to 0, no MyISAM table detected" ); - } - return; - } - - if ( !defined( $mycalc{'total_myisam_indexes'} ) ) { - badprint - "Unable to calculate MyISAM index size on MySQL server < 5.0.0"; - push( @generalrec, - "Unable to calculate MyISAM index size on MySQL server < 5.0.0" ); - return; - } - if ( $mycalc{'pct_key_buffer_used'} == 0 ) { - - # No queries have run that would use keys - infoprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" - . hr_bytes( $myvar{'key_buffer_size'} - - $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ) - . " used / " - . hr_bytes( $myvar{'key_buffer_size'} ) - . " cache)"; - infoprint "No SQL statement based on MyISAM table(s) detected ...."; - return; - } - - # Key buffer usage - if ( $mycalc{'pct_key_buffer_used'} < 90 ) { - badprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" - . hr_bytes( $myvar{'key_buffer_size'} - - $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ) - . " used / " - . hr_bytes( $myvar{'key_buffer_size'} ) - . " cache)"; - - push( - @adjvars, - "key_buffer_size (\~ " - . hr_num( - $myvar{'key_buffer_size'} * - $mycalc{'pct_key_buffer_used'} / 100 - ) - . ")" - ); - } - else { - goodprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" - . hr_bytes( $myvar{'key_buffer_size'} - - $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ) - . " used / " - . hr_bytes( $myvar{'key_buffer_size'} ) - . " cache)"; - } - - # Key buffer size / total MyISAM indexes - if ( $myvar{'key_buffer_size'} < $mycalc{'total_myisam_indexes'} - && $mycalc{'pct_keys_from_mem'} < 95 ) - { - badprint "Key buffer size / total MyISAM indexes: " - . hr_bytes( $myvar{'key_buffer_size'} ) . "/" - . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; - push( @adjvars, - "key_buffer_size (> " - . hr_bytes( $mycalc{'total_myisam_indexes'} ) - . ")" ); - } - else { - goodprint "Key buffer size / total MyISAM indexes: " - . hr_bytes( $myvar{'key_buffer_size'} ) . "/" - . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; - } - if ( $mystat{'Key_read_requests'} > 0 ) { - if ( $mycalc{'pct_keys_from_mem'} < 95 ) { - badprint - "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" - . hr_num( $mystat{'Key_read_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_reads'} ) - . " reads)"; - } - else { - goodprint - "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" - . hr_num( $mystat{'Key_read_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_reads'} ) - . " reads)"; - } - } - - # No queries have run that would use keys - debugprint "Key buffer size / total MyISAM indexes: " - . hr_bytes( $myvar{'key_buffer_size'} ) . "/" - . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; - if ( $mystat{'Key_write_requests'} > 0 ) { - if ( $mycalc{'pct_wkeys_from_mem'} < 95 ) { - badprint - "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" - . hr_num( $mystat{'Key_write_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_writes'} ) - . " writes)"; - } - else { - goodprint - "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" - . hr_num( $mystat{'Key_write_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_writes'} ) - . " writes)"; - } - } - else { - # No queries have run that would use keys - debugprint - "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" - . hr_num( $mystat{'Key_write_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_writes'} ) - . " writes)"; - } -} - -# Recommendations for ThreadPool -sub mariadb_threadpool { - subheaderprint "ThreadPool Metrics"; - - # MariaDB - unless ( defined $myvar{'have_threadpool'} - && $myvar{'have_threadpool'} eq "YES" ) - { - infoprint "ThreadPool stat is disabled."; - return; - } - infoprint "ThreadPool stat is enabled."; - infoprint "Thread Pool Size: " . $myvar{'thread_pool_size'} . " thread(s)."; - - if ( $myvar{'version'} =~ /percona/i - or $myvar{'version_comment'} =~ /percona/i ) - { - my $np = cpu_cores; - if ( $myvar{'thread_pool_size'} >= $np - and $myvar{'thread_pool_size'} < ( $np * 1.5 ) ) - { - goodprint - "thread_pool_size for Percona between 1 and 1.5 times number of CPUs (" - . $np . " and " - . ( $np * 1.5 ) . ")"; - } - else { - badprint - "thread_pool_size for Percona between 1 and 1.5 times number of CPUs (" - . $np . " and " - . ( $np * 1.5 ) . ")"; - push( @adjvars, - "thread_pool_size between " - . $np . " and " - . ( $np * 1.5 ) - . " for InnoDB usage" ); - } - return; - } - - if ( $myvar{'version'} =~ /mariadb/i ) { - infoprint "Using default value is good enough for your version (" - . $myvar{'version'} . ")"; - return; - } - - if ( $myvar{'have_innodb'} eq 'YES' ) { - if ( $myvar{'thread_pool_size'} < 16 - or $myvar{'thread_pool_size'} > 36 ) - { - badprint - "thread_pool_size between 16 and 36 when using InnoDB storage engine."; - push( @generalrec, - "Thread pool size for InnoDB usage (" - . $myvar{'thread_pool_size'} - . ")" ); - push( @adjvars, - "thread_pool_size between 16 and 36 for InnoDB usage" ); - } - else { - goodprint - "thread_pool_size between 16 and 36 when using InnoDB storage engine."; - } - return; - } - if ( $myvar{'have_isam'} eq 'YES' ) { - if ( $myvar{'thread_pool_size'} < 4 or $myvar{'thread_pool_size'} > 8 ) - { - badprint - "thread_pool_size between 4 and 8 when using MyISAM storage engine."; - push( @generalrec, - "Thread pool size for MyISAM usage (" - . $myvar{'thread_pool_size'} - . ")" ); - push( @adjvars, - "thread_pool_size between 4 and 8 for MyISAM usage" ); - } - else { - goodprint - "thread_pool_size between 4 and 8 when using MyISAM storage engine."; - } - } -} - -sub get_pf_memory { - - # Performance Schema - return 0 unless defined $myvar{'performance_schema'}; - return 0 if $myvar{'performance_schema'} eq 'OFF'; - - my @infoPFSMemory = grep { /\tperformance_schema[.]memory\t/msx } - select_array("SHOW ENGINE PERFORMANCE_SCHEMA STATUS"); - @infoPFSMemory == 1 || return 0; - $infoPFSMemory[0] =~ s/.*\s+(\d+)$/$1/g; - return $infoPFSMemory[0]; -} - -# Recommendations for Performance Schema -sub mysql_pfs { - subheaderprint "Performance schema"; - - # Performance Schema - debugprint "Performance schema is " . $myvar{'performance_schema'}; - $myvar{'performance_schema'} = 'OFF' - unless defined( $myvar{'performance_schema'} ); - if ( $myvar{'performance_schema'} eq 'OFF' ) { - badprint "Performance_schema should be activated."; - push( @adjvars, "performance_schema=ON" ); - push( @generalrec, - "Performance schema should be activated for better diagnostics" ); - } - if ( $myvar{'performance_schema'} eq 'ON' ) { - infoprint "Performance_schema is activated."; - debugprint "Performance schema is " . $myvar{'performance_schema'}; - infoprint "Memory used by Performance_schema: " - . hr_bytes( get_pf_memory() ); - } - - unless ( grep /^sys$/, select_array("SHOW DATABASES") ) { - infoprint "Sys schema is not installed."; - push( @generalrec, - mysql_version_ge( 10, 0 ) - ? "Consider installing Sys schema from https://github.com/FromDual/mariadb-sys for MariaDB" - : "Consider installing Sys schema from https://github.com/mysql/mysql-sys for MySQL" - ) unless ( mysql_version_le( 5, 6 ) ); - - return; - } - infoprint "Sys schema is installed."; - return if ( $opt{pfstat} == 0 or $myvar{'performance_schema'} ne 'ON' ); - - infoprint "Sys schema Version: " - . select_one("select sys_version from sys.version"); - - # Store all sys schema in dumpdir if defined - if ( defined $opt{dumpdir} and -d "$opt{dumpdir}" ) { - for my $sys_view ( select_array('use sys;show tables;') ) { - infoprint "Dumping $sys_view into $opt{dumpdir}"; - my $sys_view_table = $sys_view; - $sys_view_table =~ s/\$/\\\$/g; - select_csv_file( "$opt{dumpdir}/sys_$sys_view.csv", - 'select * from sys.\`' . $sys_view_table . '\`' ); - } - return; - - #exit 0 if ( $opt{stop} == 1 ); - } - - # Top user per connection - subheaderprint "Performance schema: Top 5 user per connection"; - my $nbL = 1; - for my $lQuery ( - select_array( - 'select user, total_connections from sys.user_summary order by total_connections desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery conn(s)"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top user per statement - subheaderprint "Performance schema: Top 5 user per statement"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, statements from sys.user_summary order by statements desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery stmt(s)"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top user per statement latency - subheaderprint "Performance schema: Top 5 user per statement latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, statement_avg_latency from sys.x\\$user_summary order by statement_avg_latency desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top user per lock latency - subheaderprint "Performance schema: Top 5 user per lock latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, lock_latency from sys.x\\$user_summary_by_statement_latency order by lock_latency desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top user per full scans - subheaderprint "Performance schema: Top 5 user per nb full scans"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, full_scans from sys.x\\$user_summary_by_statement_latency order by full_scans desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top user per row_sent - subheaderprint "Performance schema: Top 5 user per rows sent"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, rows_sent from sys.x\\$user_summary_by_statement_latency order by rows_sent desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top user per row modified - subheaderprint "Performance schema: Top 5 user per rows modified"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, rows_affected from sys.x\\$user_summary_by_statement_latency order by rows_affected desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top user per io - subheaderprint "Performance schema: Top 5 user per IO"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, file_ios from sys.x\\$user_summary order by file_ios desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top user per io latency - subheaderprint "Performance schema: Top 5 user per IO latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, file_io_latency from sys.x\\$user_summary order by file_io_latency desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per connection - subheaderprint "Performance schema: Top 5 host per connection"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, total_connections from sys.x\\$host_summary order by total_connections desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery conn(s)"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per statement - subheaderprint "Performance schema: Top 5 host per statement"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, statements from sys.x\\$host_summary order by statements desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery stmt(s)"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per statement latency - subheaderprint "Performance schema: Top 5 host per statement latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, statement_avg_latency from sys.x\\$host_summary order by statement_avg_latency desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per lock latency - subheaderprint "Performance schema: Top 5 host per lock latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, lock_latency from sys.x\\$host_summary_by_statement_latency order by lock_latency desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per full scans - subheaderprint "Performance schema: Top 5 host per nb full scans"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, full_scans from sys.x\\$host_summary_by_statement_latency order by full_scans desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per rows sent - subheaderprint "Performance schema: Top 5 host per rows sent"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, rows_sent from sys.x\\$host_summary_by_statement_latency order by rows_sent desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per rows modified - subheaderprint "Performance schema: Top 5 host per rows modified"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, rows_affected from sys.x\\$host_summary_by_statement_latency order by rows_affected desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per io - subheaderprint "Performance schema: Top 5 host per io"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, file_ios from sys.x\\$host_summary order by file_ios desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top 5 host per io latency - subheaderprint "Performance schema: Top 5 host per io latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, file_io_latency from sys.x\\$host_summary order by file_io_latency desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top IO type order by total io - subheaderprint "Performance schema: Top IO type order by total io"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select substring(event_name,14), SUM(total)AS total from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY total DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery i/o"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top IO type order by total latency - subheaderprint "Performance schema: Top IO type order by total latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select substring(event_name,14), ROUND(SUM(total_latency),1) AS total_latency from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY total_latency DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top IO type order by max latency - subheaderprint "Performance schema: Top IO type order by max latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select substring(event_name,14), MAX(max_latency) as max_latency from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY max_latency DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top Stages order by total io - subheaderprint "Performance schema: Top Stages order by total io"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select substring(event_name,7), SUM(total)AS total from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY total DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery i/o"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top Stages order by total latency - subheaderprint "Performance schema: Top Stages order by total latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select substring(event_name,7), ROUND(SUM(total_latency),1) AS total_latency from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY total_latency DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top Stages order by avg latency - subheaderprint "Performance schema: Top Stages order by avg latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select substring(event_name,7), MAX(avg_latency) as avg_latency from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY avg_latency DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top host per table scans - subheaderprint "Performance schema: Top 5 host per table scans"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select host, table_scans from sys.x\\$host_summary order by table_scans desc LIMIT 5' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # InnoDB Buffer Pool by schema - subheaderprint "Performance schema: InnoDB Buffer Pool by schema"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select object_schema, allocated, data, pages from sys.x\\$innodb_buffer_stats_by_schema ORDER BY pages DESC' - ) - ) - { - infoprint " +-- $nbL: $lQuery page(s)"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # InnoDB Buffer Pool by table - subheaderprint "Performance schema: 40 InnoDB Buffer Pool by table"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select object_schema, object_name, allocated,data, pages from sys.x\\$innodb_buffer_stats_by_table ORDER BY pages DESC LIMIT 40' - ) - ) - { - infoprint " +-- $nbL: $lQuery page(s)"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Process per allocated memory - subheaderprint "Performance schema: Process per time"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, Command AS PROC, time from sys.x\\$processlist ORDER BY time DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # InnoDB Lock Waits - subheaderprint "Performance schema: InnoDB Lock Waits"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select wait_age_secs, locked_table, locked_type, waiting_query from sys.x\\$innodb_lock_waits order by wait_age_secs DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Threads IO Latency - subheaderprint "Performance schema: Thread IO Latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select user, total_latency, max_latency from sys.x\\$io_by_thread_by_latency order by total_latency DESC;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # High Cost SQL statements - subheaderprint "Performance schema: Top 15 Most latency statements"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select LEFT(query, 120), avg_latency from sys.x\\$statement_analysis order by avg_latency desc LIMIT 15' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top 5% slower queries - subheaderprint "Performance schema: Top 15 slower queries"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select LEFT(query, 120), exec_count from sys.x\\$statements_with_runtimes_in_95th_percentile order by exec_count desc LIMIT 15' - ) - ) - { - infoprint " +-- $nbL: $lQuery s"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top 10 nb statement type - subheaderprint "Performance schema: Top 15 nb statement type"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select statement, sum(total) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top statement by total latency - subheaderprint "Performance schema: Top 15 statement by total latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select statement, sum(total_latency) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top statement by lock latency - subheaderprint "Performance schema: Top 15 statement by lock latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select statement, sum(lock_latency) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top statement by full scans - subheaderprint "Performance schema: Top 15 statement by full scans"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select statement, sum(full_scans) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top statement by rows sent - subheaderprint "Performance schema: Top 15 statement by rows sent"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select statement, sum(rows_sent) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Top statement by rows modified - subheaderprint "Performance schema: Top 15 statement by rows modified"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select statement, sum(rows_affected) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Use temporary tables - subheaderprint "Performance schema: 15 sample queries using temp table"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select left(query, 120) from sys.x\\$statements_with_temp_tables LIMIT 15' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Unused Indexes - subheaderprint "Performance schema: Unused indexes"; - $nbL = 1; - for my $lQuery ( - select_array( - "select \* from sys.schema_unused_indexes where object_schema not in ('performance_schema')" - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Full table scans - subheaderprint "Performance schema: Tables with full table scans"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select * from sys.x\\$schema_tables_with_full_table_scans order by rows_full_scanned DESC' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Latest file IO by latency - subheaderprint "Performance schema: Latest File IO by latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select thread, file, latency, operation from sys.x\\$latest_file_io ORDER BY latency LIMIT 10;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # FILE by IO read bytes - subheaderprint "Performance schema: File by IO read bytes"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select file, total_read from sys.x\\$io_global_by_file_by_bytes order by total_read DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # FILE by IO written bytes - subheaderprint "Performance schema: File by IO written bytes"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select file, total_written from sys.x\\$io_global_by_file_by_bytes order by total_written DESC LIMIT 15' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # file per IO total latency - subheaderprint "Performance schema: File per IO total latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select file, total_latency from sys.x\\$io_global_by_file_by_latency ORDER BY total_latency DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # file per IO read latency - subheaderprint "Performance schema: file per IO read latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select file, read_latency from sys.x\\$io_global_by_file_by_latency ORDER BY read_latency DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # file per IO write latency - subheaderprint "Performance schema: file per IO write latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select file, write_latency from sys.x\\$io_global_by_file_by_latency ORDER BY write_latency DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Event Wait by read bytes - subheaderprint "Performance schema: Event Wait by read bytes"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select event_name, total_read from sys.x\\$io_global_by_wait_by_bytes order by total_read DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Event Wait by write bytes - subheaderprint "Performance schema: Event Wait written bytes"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select event_name, total_written from sys.x\\$io_global_by_wait_by_bytes order by total_written DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # event per wait total latency - subheaderprint "Performance schema: event per wait total latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select event_name, total_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY total_latency DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # event per wait read latency - subheaderprint "Performance schema: event per wait read latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select event_name, read_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY read_latency DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # event per wait write latency - subheaderprint "Performance schema: event per wait write latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select event_name, write_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY write_latency DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - #schema_index_statistics - # TOP 15 most read index - subheaderprint "Performance schema: Top 15 most read indexes"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name,index_name, rows_selected from sys.x\\$schema_index_statistics ORDER BY ROWs_selected DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 most used index - subheaderprint "Performance schema: Top 15 most modified indexes"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name,index_name, rows_inserted+rows_updated+rows_deleted AS changes from sys.x\\$schema_index_statistics ORDER BY rows_inserted+rows_updated+rows_deleted DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 high read latency index - subheaderprint "Performance schema: Top 15 high read latency index"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name,index_name, select_latency from sys.x\\$schema_index_statistics ORDER BY select_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 high insert latency index - subheaderprint "Performance schema: Top 15 most modified indexes"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name,index_name, insert_latency from sys.x\\$schema_index_statistics ORDER BY insert_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 high update latency index - subheaderprint "Performance schema: Top 15 high update latency index"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name,index_name, update_latency from sys.x\\$schema_index_statistics ORDER BY update_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 high delete latency index - subheaderprint "Performance schema: Top 15 high delete latency index"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name,index_name, delete_latency from sys.x\\$schema_index_statistics ORDER BY delete_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 most read tables - subheaderprint "Performance schema: Top 15 most read tables"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name, rows_fetched from sys.x\\$schema_table_statistics ORDER BY ROWs_fetched DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 most used tables - subheaderprint "Performance schema: Top 15 most modified tables"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name, rows_inserted+rows_updated+rows_deleted AS changes from sys.x\\$schema_table_statistics ORDER BY rows_inserted+rows_updated+rows_deleted DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 high read latency tables - subheaderprint "Performance schema: Top 15 high read latency tables"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name, fetch_latency from sys.x\\$schema_table_statistics ORDER BY fetch_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 high insert latency tables - subheaderprint "Performance schema: Top 15 high insert latency tables"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name, insert_latency from sys.x\\$schema_table_statistics ORDER BY insert_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 high update latency tables - subheaderprint "Performance schema: Top 15 high update latency tables"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name, update_latency from sys.x\\$schema_table_statistics ORDER BY update_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # TOP 15 high delete latency tables - subheaderprint "Performance schema: Top 15 high delete latency tables"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select table_schema, table_name, delete_latency from sys.x\\$schema_table_statistics ORDER BY delete_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - # Redundant indexes - subheaderprint "Performance schema: Redundant indexes"; - $nbL = 1; - for my $lQuery ( - select_array('use sys;select * from schema_redundant_indexes;') ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Table not using InnoDB buffer"; - $nbL = 1; - for my $lQuery ( - select_array( - ' Select table_schema, table_name from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NULL;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 Tables using InnoDB buffer"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select table_schema,table_name,innodb_buffer_allocated from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NOT NULL ORDER BY innodb_buffer_allocated DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 Tables with InnoDB buffer free"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select table_schema,table_name,innodb_buffer_free from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NOT NULL ORDER BY innodb_buffer_free DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 Most executed queries"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), exec_count from sys.x\\$statement_analysis order by exec_count DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint - "Performance schema: Latest SQL queries in errors or warnings"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select LEFT(query, 120), last_seen from sys.x\\$statements_with_errors_or_warnings ORDER BY last_seen LIMIT 40;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 20 queries with full table scans"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_full_table_scans order BY exec_count DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Last 50 queries with full table scans"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_full_table_scans order BY last_seen DESC LIMIT 50;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 reader queries (95% percentile)"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), rows_sent from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY ROWs_sent DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint - "Performance schema: Top 15 most row look queries (95% percentile)"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), rows_examined AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY rows_examined DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint - "Performance schema: Top 15 total latency queries (95% percentile)"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY total_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint - "Performance schema: Top 15 max latency queries (95% percentile)"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), max_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY max_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint - "Performance schema: Top 15 average latency queries (95% percentile)"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), avg_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY avg_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 20 queries with sort"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_sorting order BY exec_count DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Last 50 queries with sort"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_sorting order BY last_seen DESC LIMIT 50;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 row sorting queries with sort"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), rows_sorted from sys.x\\$statements_with_sorting ORDER BY ROWs_sorted DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 total latency queries with sort"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_sorting ORDER BY total_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 merge queries with sort"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), sort_merge_passes AS search from sys.x\\$statements_with_sorting ORDER BY sort_merge_passes DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint - "Performance schema: Top 15 average sort merges queries with sort"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), avg_sort_merges AS search from sys.x\\$statements_with_sorting ORDER BY avg_sort_merges DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 scans queries with sort"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), sorts_using_scans AS search from sys.x\\$statements_with_sorting ORDER BY sorts_using_scans DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 range queries with sort"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), sort_using_range AS search from sys.x\\$statements_with_sorting ORDER BY sort_using_range DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - ################################################################################## - - #statements_with_temp_tables - - #mysql> desc statements_with_temp_tables; - #+--------------------------+---------------------+------+-----+---------------------+-------+ - #| Field | Type | Null | Key | Default | Extra | - #+--------------------------+---------------------+------+-----+---------------------+-------+ - #| query | longtext | YES | | NULL | | - #| db | varchar(64) | YES | | NULL | | - #| exec_count | bigint(20) unsigned | NO | | NULL | | - #| total_latency | text | YES | | NULL | | - #| memory_tmp_tables | bigint(20) unsigned | NO | | NULL | | - #| disk_tmp_tables | bigint(20) unsigned | NO | | NULL | | - #| avg_tmp_tables_per_query | decimal(21,0) | NO | | 0 | | - #| tmp_tables_to_disk_pct | decimal(24,0) | NO | | 0 | | - #| first_seen | timestamp | NO | | 0000-00-00 00:00:00 | | - #| last_seen | timestamp | NO | | 0000-00-00 00:00:00 | | - #| digest | varchar(32) | YES | | NULL | | - #+--------------------------+---------------------+------+-----+---------------------+-------+ - #11 rows in set (0,01 sec)# - # - subheaderprint "Performance schema: Top 20 queries with temp table"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_temp_tables order BY exec_count DESC LIMIT 20;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Last 50 queries with temp table"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_temp_tables order BY last_seen DESC LIMIT 50;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint - "Performance schema: Top 15 total latency queries with temp table"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_temp_tables ORDER BY total_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 queries with temp table to disk"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select db, LEFT(query, 120), disk_tmp_tables from sys.x\\$statements_with_temp_tables ORDER BY disk_tmp_tables DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - ################################################################################## - #wait_classes_global_by_latency - - #mysql> select * from wait_classes_global_by_latency; - #-----------------+-------+---------------+-------------+-------------+-------------+ - # event_class | total | total_latency | min_latency | avg_latency | max_latency | - #-----------------+-------+---------------+-------------+-------------+-------------+ - # wait/io/file | 15381 | 1.23 s | 0 ps | 80.12 us | 230.64 ms | - # wait/io/table | 59 | 7.57 ms | 5.45 us | 128.24 us | 3.95 ms | - # wait/lock/table | 69 | 3.22 ms | 658.84 ns | 46.64 us | 1.10 ms | - #-----------------+-------+---------------+-------------+-------------+-------------+ - # rows in set (0,00 sec) - - subheaderprint "Performance schema: Top 15 class events by number"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select event_class, total from sys.x\\$wait_classes_global_by_latency ORDER BY total DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 30 events by number"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select events, total from sys.x\\$waits_global_by_latency ORDER BY total DESC LIMIT 30;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 class events by total latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select event_class, total_latency from sys.x\\$wait_classes_global_by_latency ORDER BY total_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 30 events by total latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'use sys;select events, total_latency from sys.x\\$waits_global_by_latency ORDER BY total_latency DESC LIMIT 30;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 15 class events by max latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select event_class, max_latency from sys.x\\$wait_classes_global_by_latency ORDER BY max_latency DESC LIMIT 15;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - - subheaderprint "Performance schema: Top 30 events by max latency"; - $nbL = 1; - for my $lQuery ( - select_array( - 'select events, max_latency from sys.x\\$waits_global_by_latency ORDER BY max_latency DESC LIMIT 30;' - ) - ) - { - infoprint " +-- $nbL: $lQuery"; - $nbL++; - } - infoprint "No information found or indicators deactivated." - if ( $nbL == 1 ); - -} - -# Recommendations for Aria Engine -sub mariadb_aria { - subheaderprint "Aria Metrics"; - - # Aria - if ( !defined $myvar{'have_aria'} ) { - infoprint "Aria Storage Engine not available."; - return; - } - if ( $myvar{'have_aria'} ne "YES" ) { - infoprint "Aria Storage Engine is disabled."; - return; - } - infoprint "Aria Storage Engine is enabled."; - - # Aria pagecache - if ( !defined( $mycalc{'total_aria_indexes'} ) ) { - push( @generalrec, - "Unable to calculate Aria index size on MySQL server" ); - } - else { - if ( - $myvar{'aria_pagecache_buffer_size'} < $mycalc{'total_aria_indexes'} - && $mycalc{'pct_aria_keys_from_mem'} < 95 ) - { - badprint "Aria pagecache size / total Aria indexes: " - . hr_bytes( $myvar{'aria_pagecache_buffer_size'} ) . "/" - . hr_bytes( $mycalc{'total_aria_indexes'} ) . ""; - push( @adjvars, - "aria_pagecache_buffer_size (> " - . hr_bytes( $mycalc{'total_aria_indexes'} ) - . ")" ); - } - else { - goodprint "Aria pagecache size / total Aria indexes: " - . hr_bytes( $myvar{'aria_pagecache_buffer_size'} ) . "/" - . hr_bytes( $mycalc{'total_aria_indexes'} ) . ""; - } - if ( $mystat{'Aria_pagecache_read_requests'} > 0 ) { - if ( $mycalc{'pct_aria_keys_from_mem'} < 95 ) { - badprint - "Aria pagecache hit rate: $mycalc{'pct_aria_keys_from_mem'}% (" - . hr_num( $mystat{'Aria_pagecache_read_requests'} ) - . " cached / " - . hr_num( $mystat{'Aria_pagecache_reads'} ) - . " reads)"; - } - else { - goodprint - "Aria pagecache hit rate: $mycalc{'pct_aria_keys_from_mem'}% (" - . hr_num( $mystat{'Aria_pagecache_read_requests'} ) - . " cached / " - . hr_num( $mystat{'Aria_pagecache_reads'} ) - . " reads)"; - } - } - else { - - # No queries have run that would use keys - } - } -} - -# Recommendations for TokuDB -sub mariadb_tokudb { - subheaderprint "TokuDB Metrics"; - - # AriaDB - unless ( defined $myvar{'have_tokudb'} - && $myvar{'have_tokudb'} eq "YES" ) - { - infoprint "TokuDB is disabled."; - return; - } - infoprint "TokuDB is enabled."; - - # Not implemented -} - -# Recommendations for XtraDB -sub mariadb_xtradb { - subheaderprint "XtraDB Metrics"; - - # XtraDB - unless ( defined $myvar{'have_xtradb'} - && $myvar{'have_xtradb'} eq "YES" ) - { - infoprint "XtraDB is disabled."; - return; - } - infoprint "XtraDB is enabled."; - infoprint "Note that MariaDB 10.2 makes use of InnoDB, not XtraDB." - - # Not implemented -} - -# Recommendations for RocksDB -sub mariadb_rockdb { - subheaderprint "RocksDB Metrics"; - - # RocksDB - unless ( defined $myvar{'have_rocksdb'} - && $myvar{'have_rocksdb'} eq "YES" ) - { - infoprint "RocksDB is disabled."; - return; - } - infoprint "RocksDB is enabled."; - - # Not implemented -} - -# Recommendations for Spider -sub mariadb_spider { - subheaderprint "Spider Metrics"; - - # Spider - unless ( defined $myvar{'have_spider'} - && $myvar{'have_spider'} eq "YES" ) - { - infoprint "Spider is disabled."; - return; - } - infoprint "Spider is enabled."; - - # Not implemented -} - -# Recommendations for Connect -sub mariadb_connect { - subheaderprint "Connect Metrics"; - - # Connect - unless ( defined $myvar{'have_connect'} - && $myvar{'have_connect'} eq "YES" ) - { - infoprint "Connect is disabled."; - return; - } - infoprint "Connect is enabled."; - - # Not implemented -} - -# Perl trim function to remove whitespace from the start and end of the string -sub trim { - my $string = shift; - return "" unless defined($string); - $string =~ s/^\s+//; - $string =~ s/\s+$//; - return $string; -} - -sub get_wsrep_options { - return () unless defined $myvar{'wsrep_provider_options'}; - - my @galera_options = split /;/, $myvar{'wsrep_provider_options'}; - my $wsrep_slave_threads = $myvar{'wsrep_slave_threads'}; - push @galera_options, ' wsrep_slave_threads = ' . $wsrep_slave_threads; - @galera_options = remove_cr @galera_options; - @galera_options = remove_empty @galera_options; - - #debugprint Dumper( \@galera_options ) if $opt{debug}; - return @galera_options; -} - -sub get_gcache_memory { - my $gCacheMem = hr_raw( get_wsrep_option('gcache.size') ); - - return 0 unless defined $gCacheMem and $gCacheMem ne ''; - return $gCacheMem; -} - -sub get_wsrep_option { - my $key = shift; - return '' unless defined $myvar{'wsrep_provider_options'}; - my @galera_options = get_wsrep_options; - return '' unless scalar(@galera_options) > 0; - my @memValues = grep /\s*$key =/, @galera_options; - my $memValue = $memValues[0]; - return 0 unless defined $memValue; - $memValue =~ s/.*=\s*(.+)$/$1/g; - return $memValue; -} - -# REcommendations for Tables -sub mysql_table_structures { - return 0 unless ( $opt{structstat} > 0 ); - subheaderprint "Table structures analysis"; - - my @primaryKeysNbTables = select_array( - "Select CONCAT(c.table_schema, ',' , c.table_name) -from information_schema.columns c -join information_schema.tables t using (TABLE_SCHEMA, TABLE_NAME) -where c.table_schema not in ('sys', 'mysql', 'information_schema', 'performance_schema') - and t.table_type = 'BASE TABLE' -group by c.table_schema,c.table_name -having sum(if(c.column_key in ('PRI', 'UNI'), 1, 0)) = 0" - ); - - my $tmpContent = 'Schema,Table'; - if ( scalar(@primaryKeysNbTables) > 0 ) { - badprint "Following table(s) don't have primary key:"; - foreach my $badtable (@primaryKeysNbTables) { - badprint "\t$badtable"; - push @{ $result{'Tables without PK'} }, $badtable; - $tmpContent .= "\n$badtable"; - } - push @generalrec, - "Ensure that all table(s) get an explicit primary keys for performance, maintenance and also for replication"; - - } - else { - goodprint "All tables get a primary key"; - } - dump_into_file( "tables_without_primary_keys.csv", $tmpContent ); - - my @nonInnoDBTables = select_array( - "select CONCAT(table_schema, ',', table_name, ',', ENGINE) -FROM information_schema.tables t -WHERE ENGINE <> 'InnoDB' -and t.table_type = 'BASE TABLE' -and table_schema not in -('sys', 'mysql', 'performance_schema', 'information_schema')" - ); - $tmpContent = 'Schema,Table,Engine'; - if ( scalar(@nonInnoDBTables) > 0 ) { - badprint "Following table(s) are not InnoDB table:"; - push @generalrec, - "Ensure that all table(s) are InnoDB tables for performance and also for replication"; - foreach my $badtable (@nonInnoDBTables) { - if ( $badtable =~ /Memory/i ) { - badprint - "Table $badtable is a MEMORY table. It's suggested to use only InnoDB tables in production"; - } - else { - badprint "\t$badtable"; - } - $tmpContent .= "\n$badtable"; - } - } - else { - goodprint "All tables are InnoDB tables"; - } - dump_into_file( "tables_non_innodb.csv", $tmpContent ); - - my @nonutf8columns = select_array( - "SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', CHARacter_set_name, ',', COLLATION_name, ',', data_type, ',', CHARACTER_MAXIMUM_LENGTH) -from information_schema.columns -WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema') -and (CHARacter_set_name NOT LIKE 'utf8%' -or COLLATION_name NOT LIKE 'utf8%');" - ); - $tmpContent = - 'Schema,Table,Column, Charset, Collation, Data Type, Max Length'; - if ( scalar(@nonutf8columns) > 0 ) { - badprint "Following character columns(s) are not utf8 compliant:"; - push @generalrec, - "Ensure that all text colums(s) are UTF-8 compliant for encoding support and performance"; - foreach my $badtable (@nonutf8columns) { - badprint "\t$badtable"; - $tmpContent .= "\n$badtable"; - } - } - else { - goodprint "All columns are UTF-8 compliant"; - } - dump_into_file( "columns_non_utf8.csv", $tmpContent ); - - my @utf8columns = select_array( - "SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', CHARacter_set_name, ',', COLLATION_name, ',', data_type, ',', CHARACTER_MAXIMUM_LENGTH) -from information_schema.columns -WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema') -and (CHARacter_set_name LIKE 'utf8%' -or COLLATION_name LIKE 'utf8%');" - ); - $tmpContent = - 'Schema,Table,Column, Charset, Collation, Data Type, Max Length'; - foreach my $badtable (@utf8columns) { - $tmpContent .= "\n$badtable"; - } - dump_into_file( "columns_utf8.csv", $tmpContent ); - - my @ftcolumns = select_array( - "SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', data_type) -from information_schema.columns -WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema') -AND data_type='FULLTEXT';" - ); - $tmpContent = 'Schema,Table,Column, Data Type'; - foreach my $ctable (@ftcolumns) { - $tmpContent .= "\n$ctable"; - } - dump_into_file( "fulltext_columns.csv", $tmpContent ); - -} - -# Recommendations for Galera -sub mariadb_galera { - subheaderprint "Galera Metrics"; - - # Galera Cluster - unless ( defined $myvar{'have_galera'} - && $myvar{'have_galera'} eq "YES" ) - { - infoprint "Galera is disabled."; - return; - } - infoprint "Galera is enabled."; - debugprint "Galera variables:"; - foreach my $gvar ( keys %myvar ) { - next unless $gvar =~ /^wsrep.*/; - next if $gvar eq 'wsrep_provider_options'; - debugprint "\t" . trim($gvar) . " = " . $myvar{$gvar}; - $result{'Galera'}{'variables'}{$gvar} = $myvar{$gvar}; - } - if ( not defined( $myvar{'wsrep_on'} ) or $myvar{'wsrep_on'} ne "ON" ) { - infoprint "Galera is disabled."; - return; - } - debugprint "Galera wsrep provider Options:"; - my @galera_options = get_wsrep_options; - $result{'Galera'}{'wsrep options'} = get_wsrep_options(); - foreach my $gparam (@galera_options) { - debugprint "\t" . trim($gparam); - } - debugprint "Galera status:"; - foreach my $gstatus ( keys %mystat ) { - next unless $gstatus =~ /^wsrep.*/; - debugprint "\t" . trim($gstatus) . " = " . $mystat{$gstatus}; - $result{'Galera'}{'status'}{$gstatus} = $myvar{$gstatus}; - } - infoprint "GCache is using " - . hr_bytes_rnd( get_wsrep_option('gcache.mem_size') ); - - infoprint "CPU cores detected : " . (cpu_cores); - infoprint "wsrep_slave_threads: " . get_wsrep_option('wsrep_slave_threads'); - - if ( get_wsrep_option('wsrep_slave_threads') > ( (cpu_cores) * 4 ) - or get_wsrep_option('wsrep_slave_threads') < ( (cpu_cores) * 2 ) ) - { - badprint - "wsrep_slave_threads is not equal to 2, 3 or 4 times the number of CPU(s)"; - push @adjvars, "wsrep_slave_threads = " . ( (cpu_cores) * 4 ); - } - else { - goodprint - "wsrep_slave_threads is equal to 2, 3 or 4 times the number of CPU(s)"; - } - - if ( get_wsrep_option('wsrep_slave_threads') > 1 ) { - infoprint - "wsrep parallel slave can cause frequent inconsistency crash."; - push @adjvars, - "Set wsrep_slave_threads to 1 in case of HA_ERR_FOUND_DUPP_KEY crash on slave"; - - # check options for parallel slave - if ( get_wsrep_option('wsrep_slave_FK_checks') eq "OFF" ) { - badprint "wsrep_slave_FK_checks is off with parallel slave"; - push @adjvars, - "wsrep_slave_FK_checks should be ON when using parallel slave"; - } - - # wsrep_slave_UK_checks seems useless in MySQL source code - if ( $myvar{'innodb_autoinc_lock_mode'} != 2 ) { - badprint - "innodb_autoinc_lock_mode is incorrect with parallel slave"; - push @adjvars, - "innodb_autoinc_lock_mode should be 2 when using parallel slave"; - } - } - - if ( get_wsrep_option('gcs.fc_limit') != $myvar{'wsrep_slave_threads'} * 5 ) - { - badprint "gcs.fc_limit should be equal to 5 * wsrep_slave_threads (=" - . ( $myvar{'wsrep_slave_threads'} * 5 ) . ")"; - push @adjvars, "gcs.fc_limit= wsrep_slave_threads * 5 (=" - . ( $myvar{'wsrep_slave_threads'} * 5 ) . ")"; - } - else { - goodprint "gcs.fc_limit is equal to 5 * wsrep_slave_threads ( =" - . get_wsrep_option('gcs.fc_limit') . ")"; - } - - if ( get_wsrep_option('gcs.fc_factor') != 0.8 ) { - badprint "gcs.fc_factor should be equal to 0.8 (=" - . get_wsrep_option('gcs.fc_factor') . ")"; - push @adjvars, "gcs.fc_factor=0.8"; - } - else { - goodprint "gcs.fc_factor is equal to 0.8"; - } - if ( get_wsrep_option('wsrep_flow_control_paused') > 0.02 ) { - badprint "Fraction of time node pause flow control > 0.02"; - } - else { - goodprint - "Flow control fraction seems to be OK (wsrep_flow_control_paused <= 0.02)"; - } - - if ( $myvar{'binlog_format'} ne 'ROW' ) { - badprint "Binlog format should be in ROW mode."; - push @adjvars, "binlog_format = ROW"; - } - else { - goodprint "Binlog format is in ROW mode."; - } - if ( $myvar{'innodb_flush_log_at_trx_commit'} != 0 ) { - badprint "InnoDB flush log at each commit should be disabled."; - push @adjvars, "innodb_flush_log_at_trx_commit = 0"; - } - else { - goodprint "InnoDB flush log at each commit is disabled for Galera."; - } - - infoprint "Read consistency mode :" . $myvar{'wsrep_causal_reads'}; - - if ( defined( $myvar{'wsrep_cluster_name'} ) - and $myvar{'wsrep_on'} eq "ON" ) - { - goodprint "Galera WsREP is enabled."; - if ( defined( $myvar{'wsrep_cluster_address'} ) - and trim("$myvar{'wsrep_cluster_address'}") ne "" ) - { - goodprint "Galera Cluster address is defined: " - . $myvar{'wsrep_cluster_address'}; - my @NodesTmp = split /,/, $myvar{'wsrep_cluster_address'}; - my $nbNodes = @NodesTmp; - infoprint "There are $nbNodes nodes in wsrep_cluster_address"; - my $nbNodesSize = trim( $mystat{'wsrep_cluster_size'} ); - if ( $nbNodesSize == 3 or $nbNodesSize == 5 ) { - goodprint "There are $nbNodesSize nodes in wsrep_cluster_size."; - } - else { - badprint - "There are $nbNodesSize nodes in wsrep_cluster_size. Prefer 3 or 5 nodes architecture."; - push @generalrec, "Prefer 3 or 5 nodes architecture."; - } - - # wsrep_cluster_address doesn't include garbd nodes - if ( $nbNodes > $nbNodesSize ) { - badprint - "All cluster nodes are not detected. wsrep_cluster_size less than node count in wsrep_cluster_address"; - } - else { - goodprint "All cluster nodes detected."; - } - } - else { - badprint "Galera Cluster address is undefined"; - push @adjvars, - "set up wsrep_cluster_address variable for Galera replication"; - } - if ( defined( $myvar{'wsrep_cluster_name'} ) - and trim( $myvar{'wsrep_cluster_name'} ) ne "" ) - { - goodprint "Galera Cluster name is defined: " - . $myvar{'wsrep_cluster_name'}; - } - else { - badprint "Galera Cluster name is undefined"; - push @adjvars, - "set up wsrep_cluster_name variable for Galera replication"; - } - if ( defined( $myvar{'wsrep_node_name'} ) - and trim( $myvar{'wsrep_node_name'} ) ne "" ) - { - goodprint "Galera Node name is defined: " - . $myvar{'wsrep_node_name'}; - } - else { - badprint "Galera node name is undefined"; - push @adjvars, - "set up wsrep_node_name variable for Galera replication"; - } - if ( trim( $myvar{'wsrep_notify_cmd'} ) ne "" ) { - goodprint "Galera Notify command is defined."; - } - else { - badprint "Galera Notify command is not defined."; - push( @adjvars, - "set up parameter wsrep_notify_cmd to be notified" ); - } - if ( trim( $myvar{'wsrep_sst_method'} ) !~ "^xtrabackup.*" - and trim( $myvar{'wsrep_sst_method'} ) !~ "^mariabackup" ) - { - badprint "Galera SST method is not xtrabackup based."; - push( @adjvars, - "set up parameter wsrep_sst_method to xtrabackup based parameter" - ); - } - else { - goodprint "SST Method is based on xtrabackup."; - } - if ( - ( - defined( $myvar{'wsrep_OSU_method'} ) - && trim( $myvar{'wsrep_OSU_method'} ) eq "TOI" - ) - || ( defined( $myvar{'wsrep_osu_method'} ) - && trim( $myvar{'wsrep_osu_method'} ) eq "TOI" ) - ) - { - goodprint "TOI is default mode for upgrade."; - } - else { - badprint "Schema upgrade are not replicated automatically"; - push( @adjvars, "set up parameter wsrep_OSU_method to TOI" ); - } - infoprint "Max WsRep message : " - . hr_bytes( $myvar{'wsrep_max_ws_size'} ); - } - else { - badprint "Galera WsREP is disabled"; - } - - if ( defined( $mystat{'wsrep_connected'} ) - and $mystat{'wsrep_connected'} eq "ON" ) - { - goodprint "Node is connected"; - } - else { - badprint "Node is disconnected"; - } - if ( defined( $mystat{'wsrep_ready'} ) and $mystat{'wsrep_ready'} eq "ON" ) - { - goodprint "Node is ready"; - } - else { - badprint "Node is not ready"; - } - infoprint "Cluster status :" . $mystat{'wsrep_cluster_status'}; - if ( defined( $mystat{'wsrep_cluster_status'} ) - and $mystat{'wsrep_cluster_status'} eq "Primary" ) - { - goodprint "Galera cluster is consistent and ready for operations"; - } - else { - badprint "Cluster is not consistent and ready"; - } - if ( $mystat{'wsrep_local_state_uuid'} eq - $mystat{'wsrep_cluster_state_uuid'} ) - { - goodprint "Node and whole cluster at the same level: " - . $mystat{'wsrep_cluster_state_uuid'}; - } - else { - badprint "Node and whole cluster not the same level"; - infoprint "Node state uuid: " . $mystat{'wsrep_local_state_uuid'}; - infoprint "Cluster state uuid: " . $mystat{'wsrep_cluster_state_uuid'}; - } - if ( $mystat{'wsrep_local_state_comment'} eq 'Synced' ) { - goodprint "Node is synced with whole cluster."; - } - else { - badprint "Node is not synced"; - infoprint "Node State : " . $mystat{'wsrep_local_state_comment'}; - } - if ( $mystat{'wsrep_local_cert_failures'} == 0 ) { - goodprint "There is no certification failures detected."; - } - else { - badprint "There is " - . $mystat{'wsrep_local_cert_failures'} - . " certification failure(s)detected."; - } - - for my $key ( keys %mystat ) { - if ( $key =~ /wsrep_|galera/i ) { - debugprint "WSREP: $key = $mystat{$key}"; - } - } - - #debugprint Dumper get_wsrep_options() if $opt{debug}; -} - -# Recommendations for InnoDB -sub mysql_innodb { - subheaderprint "InnoDB Metrics"; - - # InnoDB - unless ( defined $myvar{'have_innodb'} - && $myvar{'have_innodb'} eq "YES" ) - { - infoprint "InnoDB is disabled."; - if ( mysql_version_ge( 5, 5 ) ) { - my $defengine = 'InnoDB'; - $defengine = $myvar{'default_storage_engine'} - if defined( $myvar{'default_storage_engine'} ); - badprint - "InnoDB Storage engine is disabled. $defengine is the default storage engine" - if $defengine eq 'InnoDB'; - infoprint - "InnoDB Storage engine is disabled. $defengine is the default storage engine" - if $defengine ne 'InnoDB'; - } - return; - } - infoprint "InnoDB is enabled."; - if ( !defined $enginestats{'InnoDB'} ) { - if ( $opt{skipsize} eq 1 ) { - infoprint "Skipped due to --skipsize option"; - return; - } - badprint "No tables are Innodb"; - $enginestats{'InnoDB'} = 0; - } - - if ( $opt{buffers} ne 0 ) { - infoprint "InnoDB Buffers"; - if ( defined $myvar{'innodb_buffer_pool_size'} ) { - infoprint " +-- InnoDB Buffer Pool: " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . ""; - } - if ( defined $myvar{'innodb_buffer_pool_instances'} ) { - infoprint " +-- InnoDB Buffer Pool Instances: " - . $myvar{'innodb_buffer_pool_instances'} . ""; - } - - if ( defined $myvar{'innodb_buffer_pool_chunk_size'} ) { - infoprint " +-- InnoDB Buffer Pool Chunk Size: " - . hr_bytes( $myvar{'innodb_buffer_pool_chunk_size'} ) . ""; - } - if ( defined $myvar{'innodb_additional_mem_pool_size'} ) { - infoprint " +-- InnoDB Additional Mem Pool: " - . hr_bytes( $myvar{'innodb_additional_mem_pool_size'} ) . ""; - } - if ( defined $myvar{'innodb_redo_log_capacity'} ) { - infoprint " +-- InnoDB Redo Log Capacity: " - . hr_bytes( $myvar{'innodb_redo_log_capacity'} ); - } - else { - if ( defined $myvar{'innodb_log_file_size'} ) { - infoprint " +-- InnoDB Log File Size: " - . hr_bytes( $myvar{'innodb_log_file_size'} ); - } - if ( defined $myvar{'innodb_log_files_in_group'} ) { - infoprint " +-- InnoDB Log File In Group: " - . $myvar{'innodb_log_files_in_group'}; - infoprint " +-- InnoDB Total Log File Size: " - . hr_bytes( $myvar{'innodb_log_files_in_group'} * - $myvar{'innodb_log_file_size'} ) - . "(" - . $mycalc{'innodb_log_size_pct'} - . " % of buffer pool)"; - } - else { - infoprint " +-- InnoDB Total Log File Size: " - . hr_bytes( $myvar{'innodb_log_file_size'} ) . "(" - . $mycalc{'innodb_log_size_pct'} - . " % of buffer pool)"; - } - } - if ( defined $myvar{'innodb_log_buffer_size'} ) { - infoprint " +-- InnoDB Log Buffer: " - . hr_bytes( $myvar{'innodb_log_buffer_size'} ); - } - if ( defined $mystat{'Innodb_buffer_pool_pages_free'} ) { - infoprint " +-- InnoDB Buffer Free: " - . hr_bytes( $mystat{'Innodb_buffer_pool_pages_free'} ) . ""; - } - if ( defined $mystat{'Innodb_buffer_pool_pages_total'} ) { - infoprint " +-- InnoDB Buffer Used: " - . hr_bytes( $mystat{'Innodb_buffer_pool_pages_total'} ) . ""; - } - } - - if ( defined $myvar{'innodb_thread_concurrency'} ) { - infoprint "InnoDB Thread Concurrency: " - . $myvar{'innodb_thread_concurrency'}; - } - - # InnoDB Buffer Pool Size - if ( $myvar{'innodb_file_per_table'} eq "ON" ) { - goodprint "InnoDB File per table is activated"; - } - else { - badprint "InnoDB File per table is not activated"; - push( @adjvars, "innodb_file_per_table=ON" ); - } - - # InnoDB Buffer Pool Size - if ( $arch == 32 && $myvar{'innodb_buffer_pool_size'} > 4294967295 ) { - badprint - "InnoDB Buffer Pool size limit reached for 32 bits architecture: (" - . hr_bytes(4294967295) . " )"; - push( @adjvars, - "limit innodb_buffer_pool_size under " - . hr_bytes(4294967295) - . " for 32 bits architecture" ); - } - if ( $arch == 32 && $myvar{'innodb_buffer_pool_size'} < 4294967295 ) { - goodprint "InnoDB Buffer Pool size ( " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) - . " ) under limit for 32 bits architecture: (" - . hr_bytes(4294967295) . ")"; - } - if ( $arch == 64 - && $myvar{'innodb_buffer_pool_size'} > 18446744073709551615 ) - { - badprint "InnoDB Buffer Pool size limit(" - . hr_bytes(18446744073709551615) - . ") reached for 64 bits architecture"; - push( @adjvars, - "limit innodb_buffer_pool_size under " - . hr_bytes(18446744073709551615) - . " for 64 bits architecture" ); - } - - if ( $arch == 64 - && $myvar{'innodb_buffer_pool_size'} < 18446744073709551615 ) - { - goodprint "InnoDB Buffer Pool size ( " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) - . " ) under limit for 64 bits architecture: (" - . hr_bytes(18446744073709551615) . " )"; - } - if ( $myvar{'innodb_buffer_pool_size'} > $enginestats{'InnoDB'} ) { - goodprint "InnoDB buffer pool / data size: " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . " / " - . hr_bytes( $enginestats{'InnoDB'} ) . ""; - } - else { - badprint "InnoDB buffer pool / data size: " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . " / " - . hr_bytes( $enginestats{'InnoDB'} ) . ""; - push( @adjvars, - "innodb_buffer_pool_size (>= " - . hr_bytes( $enginestats{'InnoDB'} ) - . ") if possible." ); - } - - # select round( 100* sum(allocated)/( select VARIABLE_VALUE - # FROM information_schema.global_variables - # where VARIABLE_NAME='innodb_buffer_pool_size' ) - # ,2) as "PCT ALLOC/BUFFER POOL" - #from sys.x$innodb_buffer_stats_by_table; - - if ( $opt{experimental} ) { - debugprint( 'innodb_buffer_alloc_pct: "' - . $mycalc{innodb_buffer_alloc_pct} - . '"' ); - if ( defined $mycalc{innodb_buffer_alloc_pct} - and $mycalc{innodb_buffer_alloc_pct} ne '' ) - { - if ( $mycalc{innodb_buffer_alloc_pct} < 80 ) { - badprint "Ratio Buffer Pool allocated / Buffer Pool Size: " - . $mycalc{'innodb_buffer_alloc_pct'} . '%'; - } - else { - goodprint "Ratio Buffer Pool allocated / Buffer Pool Size: " - . $mycalc{'innodb_buffer_alloc_pct'} . '%'; - } - } - } - if ( $mycalc{'innodb_log_size_pct'} < 20 - or $mycalc{'innodb_log_size_pct'} > 30 ) - { - if ( defined $myvar{'innodb_redo_log_capacity'} ) { - badprint - "Ratio InnoDB redo log capacity / InnoDB Buffer pool size (" - . $mycalc{'innodb_log_size_pct'} . "%): " - . hr_bytes( $myvar{'innodb_redo_log_capacity'} ) . " / " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) - . " should be equal to 25%"; - push( @adjvars, - "innodb_redo_log_capacity should be (=" - . hr_bytes_rnd( $myvar{'innodb_buffer_pool_size'} / 4 ) - . ") if possible, so InnoDB Redo log Capacity equals 25% of buffer pool size." - ); - push( @generalrec, - "Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" - ); - } - else { - badprint "Ratio InnoDB log file size / InnoDB Buffer pool size (" - . $mycalc{'innodb_log_size_pct'} . "%): " - . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " - . $myvar{'innodb_log_files_in_group'} . " / " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) - . " should be equal to 25%"; - push( - @adjvars, - "innodb_log_file_size should be (=" - . hr_bytes_rnd( - $myvar{'innodb_buffer_pool_size'} / - $myvar{'innodb_log_files_in_group'} / 4 - ) - . ") if possible, so InnoDB total log file size equals 25% of buffer pool size." - ); - push( @generalrec, - "Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" - ); - } - if ( mysql_version_le( 5, 6, 2 ) ) { - push( @generalrec, - "For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." - ); - } - - } - else { - if ( defined $myvar{'innodb_redo_log_capacity'} ) { - goodprint - "Ratio InnoDB Redo Log Capacity / InnoDB Buffer pool size: " - . hr_bytes( $myvar{'innodb_redo_log_capacity'} ) . "/" - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) - . " should be equal to 25%"; - } - else { - push( @generalrec, - "Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" - ); - goodprint "Ratio InnoDB log file size / InnoDB Buffer pool size: " - . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " - . $myvar{'innodb_log_files_in_group'} . "/" - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) - . " should be equal to 25%"; - } - } - - # InnoDB Buffer Pool Instances (MySQL 5.6.6+) - if ( not mysql_version_ge( 10, 4 ) - and defined( $myvar{'innodb_buffer_pool_instances'} ) ) - { - - # Bad Value if > 64 - if ( $myvar{'innodb_buffer_pool_instances'} > 64 ) { - badprint "InnoDB buffer pool instances: " - . $myvar{'innodb_buffer_pool_instances'} . ""; - push( @adjvars, "innodb_buffer_pool_instances (<= 64)" ); - } - - # InnoDB Buffer Pool Size > 1Go - if ( $myvar{'innodb_buffer_pool_size'} > 1024 * 1024 * 1024 ) { - - # InnoDB Buffer Pool Size / 1Go = InnoDB Buffer Pool Instances limited to 64 max. - - # InnoDB Buffer Pool Size > 64Go - my $max_innodb_buffer_pool_instances = - int( $myvar{'innodb_buffer_pool_size'} / ( 1024 * 1024 * 1024 ) ); - $max_innodb_buffer_pool_instances = 64 - if ( $max_innodb_buffer_pool_instances > 64 ); - - if ( $myvar{'innodb_buffer_pool_instances'} != - $max_innodb_buffer_pool_instances ) - { - badprint "InnoDB buffer pool instances: " - . $myvar{'innodb_buffer_pool_instances'} . ""; - push( @adjvars, - "innodb_buffer_pool_instances(=" - . $max_innodb_buffer_pool_instances - . ")" ); - } - else { - goodprint "InnoDB buffer pool instances: " - . $myvar{'innodb_buffer_pool_instances'} . ""; - } - - # InnoDB Buffer Pool Size < 1Go - } - else { - if ( $myvar{'innodb_buffer_pool_instances'} != 1 ) { - badprint - "InnoDB buffer pool <= 1G and Innodb_buffer_pool_instances(!=1)."; - push( @adjvars, "innodb_buffer_pool_instances (=1)" ); - } - else { - goodprint "InnoDB buffer pool instances: " - . $myvar{'innodb_buffer_pool_instances'} . ""; - } - } - } - - # InnoDB Used Buffer Pool Size vs CHUNK size - if ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) ) { - infoprint - "InnoDB Buffer Pool Chunk Size not used or defined in your version"; - } - else { - infoprint "Number of InnoDB Buffer Pool Chunk: " - . int( $myvar{'innodb_buffer_pool_size'} ) / - int( $myvar{'innodb_buffer_pool_chunk_size'} ) . " for " - . $myvar{'innodb_buffer_pool_instances'} - . " Buffer Pool Instance(s)"; - - if ( - int( $myvar{'innodb_buffer_pool_size'} ) % ( - int( $myvar{'innodb_buffer_pool_chunk_size'} ) * - int( $myvar{'innodb_buffer_pool_instances'} ) - ) eq 0 - ) - { - goodprint - "Innodb_buffer_pool_size aligned with Innodb_buffer_pool_chunk_size & Innodb_buffer_pool_instances"; - } - else { - badprint - "Innodb_buffer_pool_size aligned with Innodb_buffer_pool_chunk_size & Innodb_buffer_pool_instances"; - - #push( @adjvars, "Adjust innodb_buffer_pool_instances, innodb_buffer_pool_chunk_size with innodb_buffer_pool_size" ); - push( @adjvars, - "innodb_buffer_pool_size must always be equal to or a multiple of innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances" - ); - } - } - - # InnoDB Read efficiency - if ( defined $mycalc{'pct_read_efficiency'} - && $mycalc{'pct_read_efficiency'} < 90 ) - { - badprint "InnoDB Read buffer efficiency: " - . $mycalc{'pct_read_efficiency'} . "% (" - . $mystat{'Innodb_buffer_pool_read_requests'} - . " hits / " - . ( $mystat{'Innodb_buffer_pool_reads'} + - $mystat{'Innodb_buffer_pool_read_requests'} ) - . " total)"; - } - else { - goodprint "InnoDB Read buffer efficiency: " - . $mycalc{'pct_read_efficiency'} . "% (" - . $mystat{'Innodb_buffer_pool_read_requests'} - . " hits / " - . ( $mystat{'Innodb_buffer_pool_reads'} + - $mystat{'Innodb_buffer_pool_read_requests'} ) - . " total)"; - } - - # InnoDB Write efficiency - if ( defined $mycalc{'pct_write_efficiency'} - && $mycalc{'pct_write_efficiency'} < 90 ) - { - badprint "InnoDB Write Log efficiency: " - . abs( $mycalc{'pct_write_efficiency'} ) . "% (" - . abs( $mystat{'Innodb_log_write_requests'} - - $mystat{'Innodb_log_writes'} ) - . " hits / " - . $mystat{'Innodb_log_write_requests'} - . " total)"; - push( @adjvars, - "innodb_log_buffer_size (> " - . hr_bytes_rnd( $myvar{'innodb_log_buffer_size'} ) - . ")" ); - } - else { - goodprint "InnoDB Write Log efficiency: " - . $mycalc{'pct_write_efficiency'} . "% (" - . ( $mystat{'Innodb_log_write_requests'} - - $mystat{'Innodb_log_writes'} ) - . " hits / " - . $mystat{'Innodb_log_write_requests'} - . " total)"; - } - - # InnoDB Log Waits - $mystat{'Innodb_log_waits_computed'} = 0; - - if ( defined( $mystat{'Innodb_log_waits'} ) - and defined( $mystat{'Innodb_log_writes'} ) - and $mystat{'Innodb_log_writes'} > 0.000001 ) - { - $mystat{'Innodb_log_waits_computed'} = - $mystat{'Innodb_log_waits'} / $mystat{'Innodb_log_writes'}; - } - else { - undef $mystat{'Innodb_log_waits_computed'}; - } - - if ( defined $mystat{'Innodb_log_waits_computed'} - && $mystat{'Innodb_log_waits_computed'} > 0.000001 ) - { - badprint "InnoDB log waits: " - . percentage( $mystat{'Innodb_log_waits'}, - $mystat{'Innodb_log_writes'} ) - . "% (" - . $mystat{'Innodb_log_waits'} - . " waits / " - . $mystat{'Innodb_log_writes'} - . " writes)"; - push( @adjvars, - "innodb_log_buffer_size (> " - . hr_bytes_rnd( $myvar{'innodb_log_buffer_size'} ) - . ")" ); - } - else { - goodprint "InnoDB log waits: " - . percentage( $mystat{'Innodb_log_waits'}, - $mystat{'Innodb_log_writes'} ) - . "% (" - . $mystat{'Innodb_log_waits'} - . " waits / " - . $mystat{'Innodb_log_writes'} - . " writes)"; - } - $result{'Calculations'} = {%mycalc}; -} - -sub check_metadata_perf { - subheaderprint "Analysis Performance Metrics"; - if ( defined $myvar{'innodb_stats_on_metadata'} ) { - infoprint "innodb_stats_on_metadata: " - . $myvar{'innodb_stats_on_metadata'}; - if ( $myvar{'innodb_stats_on_metadata'} eq 'ON' ) { - badprint "Stat are updated during querying INFORMATION_SCHEMA."; - push @adjvars, "SET innodb_stats_on_metadata = OFF"; - - #Disabling innodb_stats_on_metadata - select_one("SET GLOBAL innodb_stats_on_metadata = OFF;"); - return 1; - } - } - goodprint "No stat updates during querying INFORMATION_SCHEMA."; - return 0; -} - -# Recommendations for Database metrics -sub mysql_databases { - return if ( $opt{dbstat} == 0 ); - - subheaderprint "Database Metrics"; - unless ( mysql_version_ge( 5, 5 ) ) { - infoprint - "Database metrics from information schema are missing in this version. Skipping..."; - return; - } - - @dblist = select_array( - "SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" - ); - infoprint "There is " . scalar(@dblist) . " Database(s)."; - my @totaldbinfo = split /\s/, - select_one( - "SELECT SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH), SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(TABLE_NAME), COUNT(DISTINCT(TABLE_COLLATION)), COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys');" - ); - infoprint "All User Databases:"; - infoprint " +-- TABLE : " - . select_one( - "SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')" - ) . ""; - infoprint " +-- VIEW : " - . select_one( - "SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')" - ) . ""; - infoprint " +-- INDEX : " - . select_one( - "SELECT count(distinct(concat(TABLE_NAME, TABLE_SCHEMA, INDEX_NAME))) from information_schema.STATISTICS WHERE TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')" - ) . ""; - - infoprint " +-- CHARS : " - . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " (" - . ( - join ", ", - select_array( - "select distinct(CHARACTER_SET_NAME) from information_schema.columns WHERE CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys');" - ) - ) . ")"; - infoprint " +-- COLLA : " - . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " (" - . ( - join ", ", - select_array( - "SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_COLLATION IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys');" - ) - ) . ")"; - infoprint " +-- ROWS : " - . ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] ) . ""; - infoprint " +-- DATA : " - . hr_bytes( $totaldbinfo[1] ) . "(" - . percentage( $totaldbinfo[1], $totaldbinfo[3] ) . "%)"; - infoprint " +-- INDEX : " - . hr_bytes( $totaldbinfo[2] ) . "(" - . percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%)"; - infoprint " +-- SIZE : " . hr_bytes( $totaldbinfo[3] ) . ""; - infoprint " +-- ENGINE: " - . ( $totaldbinfo[6] eq 'NULL' ? 0 : $totaldbinfo[6] ) . " (" - . ( - join ", ", - select_array( - "SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE ENGINE IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys');" - ) - ) . ")"; - - $result{'Databases'}{'All databases'}{'Rows'} = - ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] ); - $result{'Databases'}{'All databases'}{'Data Size'} = $totaldbinfo[1]; - $result{'Databases'}{'All databases'}{'Data Pct'} = - percentage( $totaldbinfo[1], $totaldbinfo[3] ) . "%"; - $result{'Databases'}{'All databases'}{'Index Size'} = $totaldbinfo[2]; - $result{'Databases'}{'All databases'}{'Index Pct'} = - percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%"; - $result{'Databases'}{'All databases'}{'Total Size'} = $totaldbinfo[3]; - print "\n" unless ( $opt{'silent'} or $opt{'json'} ); - my $nbViews = 0; - my $nbTables = 0; - - foreach (@dblist) { - my @dbinfo = split /\s/, - select_one( - "SELECT TABLE_SCHEMA, SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH), SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(DISTINCT ENGINE), COUNT(TABLE_NAME), COUNT(DISTINCT(TABLE_COLLATION)), COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' GROUP BY TABLE_SCHEMA ORDER BY TABLE_SCHEMA" - ); - next unless defined $dbinfo[0]; - - infoprint "Database: " . $dbinfo[0] . ""; - $nbTables = select_one( - "SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA='$_'" - ); - infoprint " +-- TABLE : $nbTables"; - infoprint " +-- VIEW : " - . select_one( - "SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA='$_'" - ) . ""; - infoprint " +-- INDEX : " - . select_one( - "SELECT count(distinct(concat(TABLE_NAME, TABLE_SCHEMA, INDEX_NAME))) from information_schema.STATISTICS WHERE TABLE_SCHEMA='$_'" - ) . ""; - infoprint " +-- CHARS : " - . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " (" - . ( - join ", ", - select_array( - "select distinct(CHARACTER_SET_NAME) from information_schema.columns WHERE CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA='$_';" - ) - ) . ")"; - infoprint " +-- COLLA : " - . ( $dbinfo[7] eq 'NULL' ? 0 : $dbinfo[7] ) . " (" - . ( - join ", ", - select_array( - "SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND TABLE_COLLATION IS NOT NULL;" - ) - ) . ")"; - infoprint " +-- ROWS : " - . ( !defined( $dbinfo[1] ) or $dbinfo[1] eq 'NULL' ? 0 : $dbinfo[1] ) - . ""; - infoprint " +-- DATA : " - . hr_bytes( $dbinfo[2] ) . "(" - . percentage( $dbinfo[2], $dbinfo[4] ) . "%)"; - infoprint " +-- INDEX : " - . hr_bytes( $dbinfo[3] ) . "(" - . percentage( $dbinfo[3], $dbinfo[4] ) . "%)"; - infoprint " +-- TOTAL : " . hr_bytes( $dbinfo[4] ) . ""; - infoprint " +-- ENGINE: " - . ( $dbinfo[8] eq 'NULL' ? 0 : $dbinfo[8] ) . " (" - . ( - join ", ", - select_array( - "SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND ENGINE IS NOT NULL" - ) - ) . ")"; - - foreach my $eng ( - select_array( - "SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND ENGINE IS NOT NULL" - ) - ) - { - infoprint " +-- ENGINE $eng : " - . select_one( - "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$dbinfo[0]' AND ENGINE='$eng'" - ) . " TABLE(s)"; - } - - if ( $nbTables == 0 ) { - badprint " No table in $dbinfo[0] database"; - next; - } - badprint "Index size is larger than data size for $dbinfo[0] \n" - if ( $dbinfo[2] ne 'NULL' ) - and ( $dbinfo[3] ne 'NULL' ) - and ( $dbinfo[2] < $dbinfo[3] ); - if ( $dbinfo[5] > 1 and $nbTables > 0 ) { - badprint "There are " - . $dbinfo[5] - . " storage engines. Be careful. \n"; - push @generalrec, - "Select one storage engine (InnoDB is a good choice) for all tables in $dbinfo[0] database ($dbinfo[5] engines detected)"; - } - $result{'Databases'}{ $dbinfo[0] }{'Rows'} = $dbinfo[1]; - $result{'Databases'}{ $dbinfo[0] }{'Tables'} = $dbinfo[6]; - $result{'Databases'}{ $dbinfo[0] }{'Collations'} = $dbinfo[7]; - $result{'Databases'}{ $dbinfo[0] }{'Data Size'} = $dbinfo[2]; - $result{'Databases'}{ $dbinfo[0] }{'Data Pct'} = - percentage( $dbinfo[2], $dbinfo[4] ) . "%"; - $result{'Databases'}{ $dbinfo[0] }{'Index Size'} = $dbinfo[3]; - $result{'Databases'}{ $dbinfo[0] }{'Index Pct'} = - percentage( $dbinfo[3], $dbinfo[4] ) . "%"; - $result{'Databases'}{ $dbinfo[0] }{'Total Size'} = $dbinfo[4]; - - if ( $dbinfo[7] > 1 ) { - badprint $dbinfo[7] - . " different collations for database " - . $dbinfo[0]; - push( @generalrec, - "Check all table collations are identical for all tables in " - . $dbinfo[0] - . " database." ); - } - else { - goodprint $dbinfo[7] - . " collation for " - . $dbinfo[0] - . " database."; - } - if ( $dbinfo[8] > 1 ) { - badprint $dbinfo[8] - . " different engines for database " - . $dbinfo[0]; - push( @generalrec, - "Check all table engines are identical for all tables in " - . $dbinfo[0] - . " database." ); - } - else { - goodprint $dbinfo[8] . " engine for " . $dbinfo[0] . " database."; - } - - my @distinct_column_charset = select_array( - "select DISTINCT(CHARACTER_SET_NAME) from information_schema.COLUMNS where CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA ='$_' AND CHARACTER_SET_NAME IS NOT NULL" - ); - infoprint "Charsets for $dbinfo[0] database table column: " - . join( ', ', @distinct_column_charset ); - if ( scalar(@distinct_column_charset) > 1 ) { - badprint $dbinfo[0] - . " table column(s) has several charsets defined for all text like column(s)."; - push( @generalrec, - "Limit charset for column to one charset if possible for " - . $dbinfo[0] - . " database." ); - } - else { - goodprint $dbinfo[0] - . " table column(s) has same charset defined for all text like column(s)."; - } - - my @distinct_column_collation = select_array( - "select DISTINCT(COLLATION_NAME) from information_schema.COLUMNS where COLLATION_NAME IS NOT NULL AND TABLE_SCHEMA ='$_' AND COLLATION_NAME IS NOT NULL" - ); - infoprint "Collations for $dbinfo[0] database table column: " - . join( ', ', @distinct_column_collation ); - if ( scalar(@distinct_column_collation) > 1 ) { - badprint $dbinfo[0] - . " table column(s) has several collations defined for all text like column(s)."; - push( @generalrec, - "Limit collations for column to one collation if possible for " - . $dbinfo[0] - . " database." ); - } - else { - goodprint $dbinfo[0] - . " table column(s) has same collation defined for all text like column(s)."; - } - } -} - -# Recommendations for database columns -sub mysql_tables { - return if ( $opt{tbstat} == 0 ); - - subheaderprint "Table Column Metrics"; - unless ( mysql_version_ge( 5, 5 ) ) { - infoprint - "Table column metrics from information schema are missing in this version. Skipping..."; - return; - } - if ( mysql_version_ge(8) and not mysql_version_eq(10) ) { - infoprint - "MySQL and Percona version 8.0 and greater have removed PROCEDURE ANALYSE feature"; - $opt{colstat} = 0; - infoprint "Disabling colstat parameter"; - - } - - infoprint("Dumpdir: $opt{dumpdir}"); - - # Store all information schema in dumpdir if defined - if ( defined $opt{dumpdir} and -d "$opt{dumpdir}" ) { - for my $info_s_table ( - select_array('use information_schema;show tables;') ) - { - infoprint "Dumping $info_s_table into $opt{dumpdir}"; - select_csv_file( - "$opt{dumpdir}/ifs_${info_s_table}.csv", - "select * from information_schema.$info_s_table" - ); - } - - #exit 0 if ( $opt{stop} == 1 ); - } - foreach ( select_user_dbs() ) { - my $dbname = $_; - next unless defined $_; - infoprint "Database: " . $_ . ""; - my @dbtable = select_array( - "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$dbname' AND TABLE_TYPE='BASE TABLE' ORDER BY TABLE_NAME" - ); - foreach (@dbtable) { - my $tbname = $_; - infoprint " +-- TABLE: $tbname"; - infoprint " +-- TYPE: " - . select_one( - "SELECT ENGINE FROM information_schema.tables where TABLE_schema='$dbname' AND TABLE_NAME='$tbname'" - ); - - my $selIdxReq = <<"ENDSQL"; - SELECT index_name AS idxname, - GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols, - INDEX_TYPE as type - FROM information_schema.statistics - WHERE INDEX_SCHEMA='$dbname' - AND TABLE_NAME='$tbname' - GROUP BY idxname, type -ENDSQL - my @tbidx = select_array($selIdxReq); - my $found = 0; - foreach my $idx (@tbidx) { - my @info = split /\s/, $idx; - next if $info[0] eq 'NULL'; - infoprint - " +-- Index $info[0] - Cols: $info[1] - Type: $info[2]"; - $found++; - } - if ( $found == 0 ) { - badprint("Table $dbname.$tbname has no index defined"); - push @generalrec, - "Add at least a primary key on table $dbname.$tbname"; - } - my @tbcol = select_array( - "SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname'" - ); - foreach (@tbcol) { - my $ctype = select_one( - "SELECT COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname' AND COLUMN_NAME='$_' " - ); - my $isnull = select_one( - "SELECT IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname' AND COLUMN_NAME='$_' " - ); - - my $current_type = - uc($ctype) . ( $isnull eq 'NO' ? " NOT NULL" : " NULL" ); - my $optimal_type = ''; - infoprint " +-- Column $tbname.$_: $current_type"; - if ( $opt{colstat} == 1 ) { - $optimal_type = select_str_g( "Optimal_fieldtype", - "SELECT \\`$_\\` FROM \\`$dbname\\`.\\`$tbname\\` PROCEDURE ANALYSE(100000)" - ) - unless ( mysql_version_ge(8) - and not mysql_version_eq(10) ); - } - if ( $optimal_type eq '' ) { - - #infoprint " +-- Current Fieldtype: $current_type"; - - #infoprint " Optimal Fieldtype: Not available"; - } - elsif ( $current_type ne $optimal_type - and $current_type !~ /.*DATETIME.*/ - and $current_type !~ /.*TIMESTAMP.*/ ) - { - infoprint " +-- Current Fieldtype: $current_type"; - if ( $optimal_type =~ /.*ENUM\(.*/ ) { - $optimal_type = "ENUM( ... )"; - } - infoprint " +-- Optimal Fieldtype: $optimal_type "; - if ( $optimal_type !~ /.*ENUM\(.*/ ) { - badprint - "Consider changing type for column $_ in table $dbname.$tbname"; - push( @generalrec, - "ALTER TABLE \`$dbname\`.\`$tbname\` MODIFY \`$_\` $optimal_type;" - ); - } - } - else { - goodprint "$dbname.$tbname ($_) type: $current_type"; - } - } - } - } -} - -# Recommendations for Indexes metrics -sub mysql_indexes { - return if ( $opt{idxstat} == 0 ); - - subheaderprint "Indexes Metrics"; - unless ( mysql_version_ge( 5, 5 ) ) { - infoprint - "Index metrics from information schema are missing in this version. Skipping..."; - return; - } - - # unless ( mysql_version_ge( 5, 6 ) ) { - # infoprint - #"Skip Index metrics from information schema due to erroneous information provided in this version"; - # return; - # } - my $selIdxReq = <<'ENDSQL'; -SELECT - CONCAT(t.TABLE_SCHEMA, '.', t.TABLE_NAME) AS 'table', - CONCAT(s.INDEX_NAME, '(', s.COLUMN_NAME, ')') AS 'index' - , s.SEQ_IN_INDEX AS 'seq' - , s2.max_columns AS 'maxcol' - , s.CARDINALITY AS 'card' - , t.TABLE_ROWS AS 'est_rows' - , INDEX_TYPE as type - , ROUND(((s.CARDINALITY / IFNULL(t.TABLE_ROWS, 0.01)) * 100), 2) AS 'sel' -FROM INFORMATION_SCHEMA.STATISTICS s - INNER JOIN INFORMATION_SCHEMA.TABLES t - ON s.TABLE_SCHEMA = t.TABLE_SCHEMA - AND s.TABLE_NAME = t.TABLE_NAME - INNER JOIN ( - SELECT - TABLE_SCHEMA - , TABLE_NAME - , INDEX_NAME - , MAX(SEQ_IN_INDEX) AS max_columns - FROM INFORMATION_SCHEMA.STATISTICS - WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema') - AND INDEX_TYPE <> 'FULLTEXT' - GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME - ) AS s2 - ON s.TABLE_SCHEMA = s2.TABLE_SCHEMA - AND s.TABLE_NAME = s2.TABLE_NAME - AND s.INDEX_NAME = s2.INDEX_NAME -WHERE t.TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema') -AND t.TABLE_ROWS > 10 -AND s.CARDINALITY IS NOT NULL -AND (s.CARDINALITY / IFNULL(t.TABLE_ROWS, 0.01)) < 8.00 -ORDER BY sel -LIMIT 10; -ENDSQL - my @idxinfo = select_array($selIdxReq); - infoprint "Worst selectivity indexes:"; - foreach (@idxinfo) { - debugprint "$_"; - my @info = split /\s/; - infoprint "Index: " . $info[1] . ""; - - infoprint " +-- COLUMN : " . $info[0] . ""; - infoprint " +-- NB SEQS : " . $info[2] . " sequence(s)"; - infoprint " +-- NB COLS : " . $info[3] . " column(s)"; - infoprint " +-- CARDINALITY : " . $info[4] . " distinct values"; - infoprint " +-- NB ROWS : " . $info[5] . " rows"; - infoprint " +-- TYPE : " . $info[6]; - infoprint " +-- SELECTIVITY : " . $info[7] . "%"; - - $result{'Indexes'}{ $info[1] }{'Column'} = $info[0]; - $result{'Indexes'}{ $info[1] }{'Sequence number'} = $info[2]; - $result{'Indexes'}{ $info[1] }{'Number of column'} = $info[3]; - $result{'Indexes'}{ $info[1] }{'Cardinality'} = $info[4]; - $result{'Indexes'}{ $info[1] }{'Row number'} = $info[5]; - $result{'Indexes'}{ $info[1] }{'Index Type'} = $info[6]; - $result{'Indexes'}{ $info[1] }{'Selectivity'} = $info[7]; - if ( $info[7] < 25 ) { - badprint "$info[1] has a low selectivity"; - } - } - infoprint "Indexes per database:"; - foreach my $dbname ( select_user_dbs() ) { - infoprint "Database: " . $dbname . ""; - $selIdxReq = <<"ENDSQL"; - SELECT concat(table_name, '.', index_name) AS idxname, - GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols, - SUM(CARDINALITY) as card, - INDEX_TYPE as type - FROM information_schema.statistics - WHERE INDEX_SCHEMA='$dbname' - AND index_name IS NOT NULL - GROUP BY table_name, idxname, type -ENDSQL - my $found = 0; - foreach my $idxinfo ( select_array($selIdxReq) ) { - my @info = split /\s/, $idxinfo; - next if $info[0] eq 'NULL'; - infoprint " +-- INDEX : " . $info[0]; - infoprint " +-- COLUMNS : " . $info[1]; - infoprint " +-- CARDINALITY: " . $info[2]; - infoprint " +-- TYPE : " . $info[4] if defined $info[4]; - infoprint " +-- COMMENT : " . $info[5] if defined $info[5]; - $found++; - } - my $nbTables = select_one( - "SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA='$dbname'" - ); - badprint "No index found for $dbname database" - if $found == 0 and $nbTables > 1; - push @generalrec, "Add indexes on tables from $dbname database" - if $found == 0 and $nbTables > 1; - } - return - unless ( defined( $myvar{'performance_schema'} ) - and $myvar{'performance_schema'} eq 'ON' ); - - $selIdxReq = <<'ENDSQL'; -SELECT CONCAT(object_schema, '.', object_name) AS 'table', index_name -FROM performance_schema.table_io_waits_summary_by_index_usage -WHERE index_name IS NOT NULL -AND count_star = 0 -AND index_name <> 'PRIMARY' -AND object_schema NOT IN ('mysql', 'performance_schema', 'information_schema') -ORDER BY count_star, object_schema, object_name; -ENDSQL - @idxinfo = select_array($selIdxReq); - infoprint "Unused indexes:"; - push( @generalrec, "Remove unused indexes." ) if ( scalar(@idxinfo) > 0 ); - foreach (@idxinfo) { - debugprint "$_"; - my @info = split /\s/; - badprint "Index: $info[1] on $info[0] is not used."; - push @{ $result{'Indexes'}{'Unused Indexes'} }, - $info[0] . "." . $info[1]; - } -} - -sub mysql_views { - subheaderprint "Views Metrics"; - unless ( mysql_version_ge( 5, 5 ) ) { - infoprint - "Views metrics from information schema are missing in this version. Skipping..."; - return; - } -} - -sub mysql_routines { - subheaderprint "Routines Metrics"; - unless ( mysql_version_ge( 5, 5 ) ) { - infoprint - "Routines metrics from information schema are missing in this version. Skipping..."; - return; - } -} - -sub mysql_triggers { - subheaderprint "Triggers Metrics"; - unless ( mysql_version_ge( 5, 5 ) ) { - infoprint - "Trigger metrics from information schema are missing in this version. Skipping..."; - return; - } -} - -# Take the two recommendation arrays and display them at the end of the output -sub make_recommendations { - $result{'Recommendations'} = \@generalrec; - $result{'AdjustVariables'} = \@adjvars; - subheaderprint "Recommendations"; - if ( @generalrec > 0 ) { - prettyprint "General recommendations:"; - foreach (@generalrec) { prettyprint " " . $_ . ""; } - } - if ( @adjvars > 0 ) { - prettyprint "Variables to adjust:"; - if ( $mycalc{'pct_max_physical_memory'} > 90 ) { - prettyprint - " *** MySQL's maximum memory usage is dangerously high ***\n" - . " *** Add RAM before increasing MySQL buffer variables ***"; - } - foreach (@adjvars) { prettyprint " " . $_ . ""; } - } - if ( @generalrec == 0 && @adjvars == 0 ) { - prettyprint "No additional performance recommendations are available."; - } -} - -sub close_outputfile { - close($fh) if defined($fh); -} - -sub headerprint { - prettyprint " >> MySQLTuner $tunerversion\n" - . "\t * Jean-Marie Renouard \n" - . "\t * Major Hayden \n" - . " >> Bug reports, feature requests, and downloads at http://mysqltuner.pl/\n" - . " >> Run with '--help' for additional options and output filtering"; - debugprint( "Debug: " . $opt{debug} ); - debugprint( "Experimental: " . $opt{experimental} ); -} - -sub string2file { - my $filename = shift; - my $content = shift; - open my $fh, q(>), $filename - or die - "Unable to open $filename in write mode. Please check permissions for this file or directory"; - print $fh $content if defined($content); - close $fh; - debugprint $content; -} - -sub file2array { - my $filename = shift; - debugprint "* reading $filename"; - my $fh; - open( $fh, q(<), "$filename" ) - or die "Couldn't open $filename for reading: $!\n"; - my @lines = <$fh>; - close($fh); - return @lines; -} - -sub file2string { - return join( '', file2array(@_) ); -} - -my $templateModel; -if ( $opt{'template'} ne 0 ) { - $templateModel = file2string( $opt{'template'} ); -} -else { - # DEFAULT REPORT TEMPLATE - $templateModel = <<'END_TEMPLATE'; - - - - MySQLTuner Report - - - - -

Result output

-
-    {$data}
-
- - - -END_TEMPLATE -} - -sub dump_result { - - #debugprint Dumper( \%result ) if ( $opt{'debug'} ); - debugprint "HTML REPORT: $opt{'reportfile'}"; - - if ( $opt{'reportfile'} ne 0 ) { - eval { require Text::Template }; - eval { require JSON }; - if ($@) { - badprint "Text::Template Module is needed."; - die "Text::Template Module is needed."; - } - - my $json = JSON->new->allow_nonref; - my $json_text = $json->pretty->encode( \%result ); - my %vars = ( - 'data' => \%result, - 'debug' => $json_text, - ); - my $template; - { - no warnings 'once'; - $template = Text::Template->new( - TYPE => 'STRING', - PREPEND => q{;}, - SOURCE => $templateModel, - DELIMITERS => [ '[%', '%]' ] - ) or die "Couldn't construct template: $Text::Template::ERROR"; - } - - open my $fh, q(>), $opt{'reportfile'} - or die - "Unable to open $opt{'reportfile'} in write mode. please check permissions for this file or directory"; - $template->fill_in( HASH => \%vars, OUTPUT => $fh ); - close $fh; - } - - if ( $opt{'json'} ne 0 ) { - eval { require JSON }; - if ($@) { - print "$bad JSON Module is needed.\n"; - return 1; - } - - my $json = JSON->new->allow_nonref; - print $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) - ->encode( \%result ); - - if ( $opt{'outputfile'} ne 0 ) { - unlink $opt{'outputfile'} if ( -e $opt{'outputfile'} ); - open my $fh, q(>), $opt{'outputfile'} - or die - "Unable to open $opt{'outputfile'} in write mode. please check permissions for this file or directory"; - print $fh $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) - ->encode( \%result ); - close $fh; - } - } -} - -sub which { - my $prog_name = shift; - my $path_string = shift; - my @path_array = split /:/, $ENV{'PATH'}; - - for my $path (@path_array) { - return "$path/$prog_name" if ( -x "$path/$prog_name" ); - } - - return 0; -} - -# --------------------------------------------------------------------------- -# BEGIN 'MAIN' -# --------------------------------------------------------------------------- -headerprint; # Header Print - -validate_tuner_version; # Check latest version -mysql_setup; # Gotta login first -debugprint "MySQL FINAL Client : $mysqlcmd $mysqllogin"; -debugprint "MySQL Admin FINAL Client : $mysqladmincmd $mysqllogin"; - -#exit(0); -os_setup; # Set up some OS variables -get_all_vars; # Toss variables/status into hashes -get_tuning_info; # Get information about the tuning connection -calculations; # Calculate everything we need -check_architecture; # Suggest 64-bit upgrade -check_storage_engines; # Show enabled storage engines -if ( $opt{'feature'} ne '' ) { - subheaderprint "See FEATURES.md for more information"; - no strict 'refs'; - for my $feature ( split /,/, $opt{'feature'} ) { - subheaderprint "Running feature: $opt{'feature'}"; - $feature->(); - } - make_recommendations; - exit(0); -} -validate_mysql_version; # Check current MySQL version - -system_recommendations; # Avoid too many services on the same host -log_file_recommendations; # check log file content - -check_metadata_perf; # Show parameter impacting performance during analysis -mysql_databases; # Show information about databases -mysql_tables; # Show information about table column -mysql_table_structures; # Show information about table structures - -mysql_indexes; # Show information about indexes -mysql_views; # Show information about views -mysql_triggers; # Show information about triggers -mysql_routines; # Show information about routines -security_recommendations; # Display some security recommendations -cve_recommendations; # Display related CVE - -mysql_stats; # Print the server stats -mysql_pfs; # Print Performance schema info - -mariadb_threadpool; # Print MariaDB ThreadPool stats -mysql_myisam; # Print MyISAM stats -mysql_innodb; # Print InnoDB stats -mariadb_aria; # Print MariaDB Aria stats -mariadb_tokudb; # Print MariaDB Tokudb stats -mariadb_xtradb; # Print MariaDB XtraDB stats - -#mariadb_rockdb; # Print MariaDB RockDB stats -#mariadb_spider; # Print MariaDB Spider stats -#mariadb_connect; # Print MariaDB Connect stats -mariadb_galera; # Print MariaDB Galera Cluster stats -get_replication_status; # Print replication info -make_recommendations; # Make recommendations based on stats -dump_result; # Dump result if debug is on -close_outputfile; # Close reportfile if needed - -# --------------------------------------------------------------------------- -# END 'MAIN' -# --------------------------------------------------------------------------- -1; - -__END__ - -=pod - -=encoding UTF-8 - -=head1 NAME - - MySQLTuner 2.6.2 - MySQL High Performance Tuning Script - -=head1 IMPORTANT USAGE GUIDELINES - -To run the script with the default options, run the script without arguments -Allow MySQL server to run for at least 24-48 hours before trusting suggestions -Some routines may require root level privileges (script will provide warnings) -You must provide the remote server's total memory when connecting to other servers - -=head1 CONNECTION AND AUTHENTICATION - - --host Connect to a remote host to perform tests (default: localhost) - --socket Use a different socket for a local connection - --port Port to use for connection (default: 3306) - --protocol tcp Force TCP connection instead of socket - --user Username to use for authentication - --userenv Name of env variable which contains username to use for authentication - --pass Password to use for authentication - --passenv Name of env variable which contains password to use for authentication - --ssl-ca Path to public key - --mysqladmin Path to a custom mysqladmin executable - --mysqlcmd Path to a custom mysql executable - --defaults-file Path to a custom .my.cnf - --defaults-extra-file Path to an extra custom config file - --server-log Path to explicit log file (error_log) - -=head1 PERFORMANCE AND REPORTING OPTIONS - - --skipsize Don't enumerate tables and their types/sizes (default: on) - (Recommended for servers with many tables) - --json Print result as JSON string - --prettyjson Print result as JSON formatted string - --skippassword Don't perform checks on user passwords (default: off) - --checkversion Check for updates to MySQLTuner (default: don't check) - --updateversion Check for updates to MySQLTuner and update when newer version is available (default: don't check) - --forcemem Amount of RAM installed in megabytes - --forceswap Amount of swap memory configured in megabytes - --passwordfile Path to a password file list (one password by line) - --cvefile CVE File for vulnerability checks - --outputfile Path to a output txt file - --reportfile Path to a report txt file - --template Path to a template file - --dumpdir Path to a directory where to dump information files - --feature Run a specific feature (see FEATURES section) - --dumpdir information_schema tables and sys views are dumped in CSV in this path - -=head1 OUTPUT OPTIONS - - --silent Don't output anything on screen - --verbose Print out all options (default: no verbose, dbstat, idxstat, sysstat, tbstat, pfstat) - --color Print output in color - --nocolor Don't print output in color - --noprettyicon Print output with legacy tag [OK], [!!], [--], [CMD], ... - --nogood Remove OK responses - --nobad Remove negative/suggestion responses - --noinfo Remove informational responses - --debug Print debug information - --experimental Print experimental analysis (may fail) - --nondedicated Consider server is not dedicated to Db server usage only - --noprocess Consider no other process is running - --dbstat Print database information - --nodbstat Don't print database information - --tbstat Print table information - --notbstat Don't print table information - --colstat Print column information - --nocolstat Don't print column information - --idxstat Print index information - --noidxstat Don't print index information - --nomyisamstat Don't print MyIsam information - --sysstat Print system information - --nosysstat Don't print system information - --nostructstat Don't print table structures information - --pfstat Print Performance schema - --nopfstat Don't print Performance schema - --bannedports Ports banned separated by comma (,) - --server-log Define specific error_log to analyze - --maxportallowed Number of open ports allowable on this host - --buffers Print global and per-thread buffer values - -=head1 PERLDOC - -You can find documentation for this module with the perldoc command. - - perldoc mysqltuner - -=head2 INTERNALS - -L - - Internal documentation - -=head1 AUTHORS - -Major Hayden - major@mhtx.net -Jean-Marie Renouard - jmrenouard@gmail.com - -=head1 CONTRIBUTORS - -=over 4 - -=item * - -Matthew Montgomery - -=item * - -Paul Kehrer - -=item * - -Dave Burgess - -=item * - -Jonathan Hinds - -=item * - -Mike Jackson - -=item * - -Nils Breunese - -=item * - -Shawn Ashlee - -=item * - -Luuk Vosslamber - -=item * - -Ville Skytta - -=item * - -Trent Hornibrook - -=item * - -Jason Gill - -=item * - -Mark Imbriaco - -=item * - -Greg Eden - -=item * - -Aubin Galinotti - -=item * - -Giovanni Bechis - -=item * - -Bill Bradford - -=item * - -Ryan Novosielski - -=item * - -Michael Scheidell - -=item * - -Blair Christensen - -=item * - -Hans du Plooy - -=item * - -Victor Trac - -=item * - -Everett Barnes - -=item * - -Tom Krouper - -=item * - -Gary Barrueto - -=item * - -Simon Greenaway - -=item * - -Adam Stein - -=item * - -Isart Montane - -=item * - -Baptiste M. - -=item * - -Cole Turner - -=item * - -Major Hayden - -=item * - -Joe Ashcraft - -=item * - -Jean-Marie Renouard - -=item * - -Stephan GroBberndt - -=item * - -Christian Loos - -=item * - -Long Radix - -=back - -=head1 SUPPORT - - -Bug reports, feature requests, and downloads at http://mysqltuner.pl/ - -Bug tracker can be found at https://github.com/major/MySQLTuner-perl/issues - -Maintained by Jean-Marie Renouard (jmrenouard\@gmail.com) - Licensed under GPL - -=head1 SOURCE CODE - -L - - git clone https://github.com/major/MySQLTuner-perl.git - -=head1 COPYRIGHT AND LICENSE - -Copyright (C) 2006-2023 Major Hayden - major@mhtx.net -# Copyright (C) 2015-2023 Jean-Marie Renouard - jmrenouard@gmail.com - -For the latest updates, please visit http://mysqltuner.pl/ - -Git repository available at https://github.com/major/MySQLTuner-perl - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - - See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -=cut - -# Local variables: -# indent-tabs-mode: t -# cperl-indent-level: 8 -# perl-indent-level: 8 -# End: diff --git a/misc/update/tmux/scripts/tuning-primer.sh b/misc/update/tmux/scripts/tuning-primer.sh deleted file mode 100644 index 0f393d433..000000000 --- a/misc/update/tmux/scripts/tuning-primer.sh +++ /dev/null @@ -1,1622 +0,0 @@ - -#!/bin/bash - -# vim: sw=2:et -######################################################################### -# # -# MySQL performance tuning primer script # -# Written by: Matthew Montgomery and Dan Reif # -# Report bugs to: https://github.com/BMDan/tuning-primer.sh/issues# -# Inspired by: MySQLARd (http://gert.sos.be/demo/mysqlar/) # -# Version: 1.99 Released: 2018-06-10 # -# Licenced under GPLv2 # -# # -######################################################################### - -######################################################################### -# # -# Usage: ./tuning-primer.sh [ mode ] # -# # -# Available Modes: # -# all : perform all checks (default) # -# prompt : prompt for login credentials and socket # -# and execution mode # -# mem, memory : run checks for tunable options which # -# affect memory usage # -# disk, file : run checks for options which affect # -# i/o performance or file handle limits # -# innodb : run InnoDB checks /* to be improved */ # -# misc : run checks that don't fit categories # -# well Slow Queries, Binary logs, # -# Used Connections and Worker Threads # -######################################################################### -# # -# Set this socket variable ONLY if you have multiple instances running # -# or we are unable to find your socket, and you don't want to to be # -# prompted for input each time you run this script. # -# # -######################################################################### -socket= - -function colorize() { - # As an explicit special case, "" yields sgr0 (i.e., black). - local originalcolor="${1}" - local basecolor="${originalcolor}" - - basecolor="${basecolor#bold}" - basecolor="${basecolor#so}" - - case "$basecolor" in - 'red') - tput setaf 1 ;; - 'green') - tput setaf 2 ;; - 'yellow') - tput setaf 3 ;; - 'blue') - tput setaf 4 ;; - 'magenta') - tput setaf 5 ;; - 'cyan') - tput setaf 6 ;; - 'white') - tput setaf 7 ;; - *) # Also "black": - [ "$basecolor" != "black" ] && [ -n "$basecolor" ] && echo "No such color '$basecolor'." >&2 - tput sgr0 ;; - esac - - # bold, etc. - if [ "$basecolor" != "$originalcolor" ]; then - case $originalcolor in - bold*) - tput bold ;; - so*) - tput smso ;; - *) - echo "No such color modifier '${originalcolor%$basecolor}'." >&2 ;; - esac - fi -} - -function cecho() -{ - if [ -z "${1-}" ]; then - cecho "No message passed." "${2-}" - return $? - fi - cechon "$1"$'\n' "${2-}" - return $? -} - -function cechon() -{ -## -- Function to easily print colored text -- ## - - # Color-echo. - # Argument $1 = message - # Argument $2 = color - local default_msg="No message passed." - - message=${1:-$default_msg} # Defaults to default message. - - #change it for fun - #We use pure names - color=${2:-black} # Defaults to black, if not specified. - - colorize "$color" - printf "%s" "$message" - colorize "" # Reset to normal. - -return -} - -function write_mycnf() { - # $1: Path to .my.cnf - # $2: Path to socket (optional) - # $3: username - # $4: password - local socketcomment="" - [ -z "$2" ] && socketcomment="#" - local cleanpassword="${4//\\/\\\\}" - cleanpassword="${cleanpassword//\"/\\\"}" - cat > "${1}" <&1) - local print_defaults=$($MYSQLADMIN_COMMAND --print-defaults 2>/dev/null) - - if [ "$is_up" = "mysqld is alive" ]; then - if [ "${print_defaults//*--host=*}" = "" ] && - ! [ "${print_defaults//*--host=127\.0\.0\.1*}" = "" ] && - ! [ "${print_defaults//*--host=::1*}" = "" ] && - ! [ "${print_defaults//*--host=localhost*}" = "" ]; then - cecho "WARNING: you might be connecting to a remote server. If so, some" boldred - cecho "results may be based on incorrect assumptions. See #13 on Github." boldred - fi - return 0 - fi - printf "\n" - cecho "Using login values from ~/.my.cnf" - cecho "- INITIAL LOGIN ATTEMPT FAILED -" boldred - if [ -z $prompted ] ; then - find_webmin_passwords - fi - return 1 -} - -final_login_attempt () { - is_up=$($MYSQLADMIN_COMMAND ping 2>&1) - if [ "$is_up" = "mysqld is alive" ] ; then - return 0 - else - cecho "- FINAL LOGIN ATTEMPT FAILED -" boldred - cecho "Unable to log into socket: $socket" boldred - exit 1 - fi -} - -## -- create a ~/.my.cnf and exit when all else fails -- ## -function second_login_failed() -{ - cecho "Could not auto detect login info!" - cecho "Found potential sockets: $(xargs <<< "$found_socks")" - if [ -z "$socket" ]; then - cecho " Will use client's default socket (this is normally correct)." bold - else - cecho " Choosing: $socket" red - fi - read -rp "Would you like to override my socket choice?: [y/N] " REPLY - case $REPLY in - yes | y | Y | YES) - read -rp "Socket: " socket - ;; - esac - read -rp "Do you have your login handy ? [y/N] : " REPLY - case $REPLY in - yes | y | Y | YES) - answer1='yes' - read -rp "User: " user - read -rsp "Password: " pass - - export MYSQL_COMMAND="mysql" - export MYSQLADMIN_COMMAND="mysqladmin" - - ;; - *) - cecho "Please create a valid login to MySQL" - cecho "Or, set correct values for 'user=' and 'password=' in ~/.my.cnf" - ;; - esac - cecho " " - echo "Would you like me to create a ~/.my.cnf file for you? If you answer 'N'," - read -p "then I'll create a secure, temporary one instead. [y/N] : " REPLY - case $REPLY in - yes | y | Y | YES) - answer2='yes' - if [ ! -f ~/.my.cnf ] ; then - umask 077 - write_mycnf "${HOME}/.my.cnf" "$socket" "$user" "$pass" - if [ "$answer1" != 'yes' ] ; then - exit 1 - else - final_login_attempt - return 0 - fi - else - printf "\n" - cecho "~/.my.cnf already exists!" boldred - printf "\n" - read -p "Replace ? [y/N] : " REPLY - if [ "$REPLY" = 'y' ] || [ "$REPLY" = 'Y' ] ; then - write_mycnf "${HOME}/.my.cnf" "$socket" "$user" "$pass" - if [ "$answer1" != 'yes' ] ; then - exit 1 - else - final_login_attempt - return 0 - fi - else - cecho "Please set the 'user=' and 'password=' and 'socket=' values in ~/.my.cnf" - exit 1 - fi - fi - ;; - *) - if [ "$answer1" != 'yes' ] ; then - exit 1 - else - local tempmycnf - tempmycnf="$(mktemp)" - write_mycnf "$tempmycnf" "$socket" "$user" "$pass" - export MYSQL_COMMAND="mysql --defaults-extra-file=$tempmycnf $MYSQL_COMMAND_PARAMS" - export MYSQLADMIN_COMMAND="mysqladmin --defaults-extra-file=$tempmycnf $MYSQL_COMMAND_PARAMS" - final_login_attempt - return 0 - fi - ;; - esac -} - -find_webmin_passwords () { - -## -- populate the .my.cnf file using values harvested from Webmin -- ## - - cecho "Testing for stored webmin passwords:" - if [ -f /etc/webmin/mysql/config ] ; then - user=$(grep ^login= /etc/webmin/mysql/config | cut -d "=" -f 2) - pass=$(grep ^pass= /etc/webmin/mysql/config | cut -d "=" -f 2) - if [ $user ] && [ $pass ] && [ ! -f ~/.my.cnf ] ; then - cecho "Setting login info as User: $user Password: $pass" - touch ~/.my.cnf - chmod 600 ~/.my.cnf - write_mycnf "${HOME}/.my.cnf" "" "$user" "$pass" - cecho "Retrying login" - is_up=$($MYSQLADMIN_COMMAND ping 2>&1) - if [ "$is_up" = "mysqld is alive" ] ; then - echo UP > /dev/null - else - second_login_failed - fi - echo - else - second_login_failed - echo - fi - else - cecho " None Found" boldred - second_login_failed - fi -} - -######################################################################### -# # -# Function to pull MySQL status variable # -# # -# Call using : # -# mysql_status \'Mysql_status_variable\' bash_dest_variable # -# # -######################################################################### - -mysql_status () { - local status=$($MYSQL_COMMAND -Bse "show /*!50000 global */ status like $1" | awk '{ print $2 }') - export "$2"=$status -} - -######################################################################### -# # -# Function to pull MySQL server runtime variable # -# # -# Call using : # -# mysql_variable \'Mysql_server_variable\' bash_dest_variable # -# - OR - # -# mysql_variableTSV \'Mysql_server_variable\' bash_dest_variable # -# # -######################################################################### - -mysql_variable () { - local variable=$($MYSQL_COMMAND -Bse "show /*!50000 global */ variables like $1" | awk '{ print $2 }') - export "$2"=$variable -} -mysql_variableTSV () { - local variable=$($MYSQL_COMMAND -Bse "show /*!50000 global */ variables like $1" | awk -F '\t' '{ print $2 }') - export "$2"=$variable -} - -# -- Divide two integers -- # -function divide() -{ - usage="$0 dividend divisor '$variable' scale" - if [ $1 -ge 1 ] ; then - dividend=$1 - else - cecho "Invalid Dividend" red - echo "$usage" - exit 1 - fi - if [ $2 -ge 1 ] ; then - divisor=$2 - else - cecho "Invalid Divisor" red - echo "$usage" - exit 1 - fi - if [ ! -n $3 ] ; then - cecho "Invalid variable name" red - echo "$usage" - exit 1 - fi - if [ -z $4 ] ; then - scale=2 - elif [ $4 -ge 0 ] ; then - scale=$4 - else - cecho "Invalid scale" red - echo "$usage" - exit 1 - fi - export $3=$(echo "scale=$scale; $dividend / $divisor" | bc -l) -} - -human_readable () { - -######################################################################### -# # -# Convert a value in to human readable size and populate a variable # -# with the result. # -# # -# Call using: # -# human_readable $value 'variable name' [ places of precision] # -# # -######################################################################### - - ## value=$1 - ## variable=$2 - scale=$3 - - if [ $1 -ge 1073741824 ] ; then - if [ -z $3 ] ; then - scale=2 - fi - divide $1 1073741824 "$2" $scale - unit="G" - elif [ $1 -ge 1048576 ] ; then - if [ -z $3 ] ; then - scale=0 - fi - divide $1 1048576 "$2" $scale - unit="M" - elif [ $1 -ge 1024 ] ; then - if [ -z $3 ] ; then - scale=0 - fi - divide $1 1024 "$2" $scale - unit="K" - else - export "$2"=$1 - unit="bytes" - fi - # let "$2"=$HR -} - -function human_readable_time() -{ - # Produce human readable time from a duration in seconds. - - # Remove and save any fractional component - local secs="${1%.*}" - local subsecs="${1#$secs}" - - if [ -z $1 ] || [ -z $2 ] ; then - cecho "${FUNCNAME[0]} seconds 'variable'" red - exit 1 - fi - - export $2="$((secs/86400)) days $((secs/3600%24)) hrs $((secs/60%60)) min $((secs%60))$subsecs sec" -} - -check_mysql_version () { - -## -- Print Version Info -- ## - - mysql_variable \'version\' mysql_version - mysql_variable \'version_compile_machine\' mysql_version_compile_machine - -if [ "$mysql_version_num" -lt 050000 ]; then - cecho "MySQL Version $mysql_version $mysql_version_compile_machine is EOL please upgrade to MySQL 4.1 or later" boldred -else - cecho "MySQL Version $mysql_version $mysql_version_compile_machine" -fi - - -} - -post_uptime_warning () { - -######################################################################### -# # -# Present a reminder that mysql must run for a couple of days to # -# build up good numbers in server status variables before these tuning # -# suggestions should be used. # -# # -######################################################################### - - mysql_status \'Uptime\' uptime - mysql_status \'Threads_connected\' threads - queries_per_sec="$(($questions/$uptime)).$(printf '%02d' $((100*$questions/$uptime%100)))" - human_readable_time $uptime uptimeHR - - cecho "Uptime = $uptimeHR" - cecho "Avg. qps = $queries_per_sec" - cecho "Total Questions = $questions" - cecho "Threads Connected = $threads" - echo - - if [ $uptime -gt 172800 ] ; then - cecho "Server has been running for over 48hrs." - cecho "It should be safe to follow these recommendations" - else - cechon "Warning: " boldred - cecho "Server has not been running for at least 48hrs." boldred - cecho "It may not be safe to use these recommendations" boldred - - fi - echo "" - cecho "To find out more information on how each of these" red - cecho "runtime variables effects performance visit:" red - if [ "$major_version" = '3.23' ] || [ "$major_version" = '4.0' ] || [ "$major_version" = '4.1' ] ; then - cecho "http://dev.mysql.com/doc/refman/4.1/en/server-system-variables.html" boldblue - elif [ "$major_version" = '5.0' ] || [ "$mysql_version_num" -gt '050100' ]; then - cecho "http://dev.mysql.com/doc/refman/$major_version/en/server-system-variables.html" boldblue - else - cecho "UNSUPPORTED MYSQL VERSION" boldred - exit 1 - fi - echo "" - cecho "Visit https://github.com/BMDan/tuning-primer.sh for the latest version of" boldblue - cecho "this script, or to suggest improvements." boldblue -} - -check_slow_queries () { - -## -- Slow Queries -- ## - cecho "SLOW QUERIES" boldblue - - mysql_status \'Slow_queries\' slow_queries - mysql_variable \'long_query_time\' long_query_time - mysql_variable \'log%queries\' log_slow_queries - mysql_variable \'slow_query_log\' slow_query_log - - PREFERRED_QUERY_TIME=5 - if [ -z "$log_slow_queries" ] ; then - log_slow_queries="$slow_query_log" - fi - - if [ "$log_slow_queries" = 'ON' ] ; then - cecho "The slow query log is enabled." - elif [ "$log_slow_queries" = 'OFF' ] || [ -z "$log_slow_queries" ] ; then - cechon "The slow query log is " - cechon "NOT" boldred - cecho " enabled." - return - else - cecho "Slow query log check failed; error(s): $log_slow_queries/$slow_query_log" boldred - fi - - cecho "Current long_query_time = $long_query_time sec." - cechon "Since startup, " - cechon "$slow_queries" boldred - cechon " out of " - cechon "$questions" boldred - cecho " queries have taken longer than to complete." - - if [ "${long_query_time%%.*}" -ge $PREFERRED_QUERY_TIME ] ; then - cecho "Your long_query_time may be too high, I typically set this under $PREFERRED_QUERY_TIME sec." red - elif [ "${long_query_time/.}" -eq 0 ] ; then - cechon "Your long_query_time is set to " - cechon "zero" boldred - cechon ", which will cause " - cechon "ALL queries to be logged" red - cecho "!" - cecho "If you actually WANT to log all queries, use the query log, not the slow query log." - else - cecho "Your long_query_time seems reasonable." green - fi -} - -check_binary_log () { - -## -- Binary Log -- ## - - cecho "BINARY UPDATE LOG" boldblue - - mysql_variable \'log_bin\' log_bin - mysql_variable \'max_binlog_size\' max_binlog_size - mysql_variable \'expire_logs_days\' expire_logs_days - mysql_variable \'sync_binlog\' sync_binlog - # mysql_variable \'max_binlog_cache_size\' max_binlog_cache_size - - if [ "$log_bin" = 'ON' ] ; then - cecho "The binary update log is enabled" - if [ -z "$max_binlog_size" ] ; then - cecho "The max_binlog_size is not set. The binary log will rotate when it reaches 1GB." red - fi - if [ "${expire_logs_days//.}" -eq 0 ] ; then # Turns 0.000 -> 0000. - cecho "The expire_logs_days is not set." boldred - cechon "The mysqld will retain the entire binary log until " red - cecho "RESET MASTER or PURGE MASTER LOGS commands are run manually" red - cecho "Setting expire_logs_days will allow you to remove old binary logs automatically" yellow - cecho "See http://dev.mysql.com/doc/refman/$major_version/en/purge-master-logs.html" yellow - fi - if [ "$sync_binlog" = 0 ] ; then - cecho "Binlog sync is not enabled, you could lose binlog records during a server crash" red - fi - else - cechon "The binary update log is " - cechon "NOT " boldred - cecho "enabled." - cecho "You will not be able to do point in time recovery" red - cecho "See http://dev.mysql.com/doc/refman/$major_version/en/point-in-time-recovery.html" yellow - fi -} - -check_used_connections () { - -## -- Used Connections -- ## - - mysql_variable \'max_connections\' max_connections - mysql_status \'Max_used_connections\' max_used_connections - mysql_status \'Threads_connected\' threads_connected - - connections_ratio=$(($max_used_connections*100/$max_connections)) - - cecho "MAX CONNECTIONS" boldblue - cecho "Current max_connections = $max_connections" - cecho "Current threads_connected = $threads_connected" - cecho "Historic max_used_connections = $max_used_connections" - cechon "The number of used connections is " - if [ $connections_ratio -ge 85 ] ; then - txt_color=red - error=1 - elif [ $connections_ratio -le 10 ] ; then - txt_color=red - error=2 - else - txt_color=green - error=0 - fi - # cechon "$max_used_connections " $txt_color - # cechon "which is " - cechon "$connections_ratio% " $txt_color - cecho "of the configured maximum." - - if [ $error -eq 1 ] ; then - cecho "You should raise max_connections" $txt_color - elif [ $error -eq 2 ] ; then - cecho "You are using less than 10% of your configured max_connections." $txt_color - cecho "Lowering max_connections could help to avoid an over-allocation of memory" $txt_color - cecho "See \"MEMORY USAGE\" section to make sure you are not over-allocating" $txt_color - else - cecho "Your max_connections variable seems to be fine." $txt_color - fi - unset txt_color -} - -check_threads() { - -## -- Worker Threads -- ## - - cecho "WORKER THREADS" boldblue - - mysql_status \'Threads_created\' threads_created1 - sleep 1 - mysql_status \'Threads_created\' threads_created2 - - mysql_status \'Threads_cached\' threads_cached - mysql_status \'Uptime\' uptime - mysql_variable \'thread_cache_size\' thread_cache_size - - historic_threads_per_sec=$(($threads_created1/$uptime)) - current_threads_per_sec=$(($threads_created2-$threads_created1)) - - cecho "Current thread_cache_size = $thread_cache_size" - cecho "Current threads_cached = $threads_cached" - cecho "Current threads_per_sec = $current_threads_per_sec" - cecho "Historic threads_per_sec = $historic_threads_per_sec" - - if [ $historic_threads_per_sec -ge 2 ] && [ $threads_cached -le 1 ] ; then - cecho "Threads created per/sec are overrunning threads cached" red - cecho "You should raise thread_cache_size" red - elif [ $current_threads_per_sec -ge 2 ] ; then - cecho "Threads created per/sec are overrunning threads cached" red - cecho "You should raise thread_cache_size" red - else - cecho "Your thread_cache_size is fine" green - fi -} - -check_key_buffer_size () { - -## -- Key buffer Size -- ## - - cecho "KEY BUFFER" boldblue - - mysql_status \'Key_read_requests\' key_read_requests - mysql_status \'Key_reads\' key_reads - mysql_status \'Key_blocks_used\' key_blocks_used - mysql_status \'Key_blocks_unused\' key_blocks_unused - mysql_variable \'key_cache_block_size\' key_cache_block_size - mysql_variable \'key_buffer_size\' key_buffer_size - mysql_variable \'datadir\' datadir - mysql_variable \'version_compile_machine\' mysql_version_compile_machine - myisam_indexes=$($MYSQL_COMMAND -Bse "/*!50000 SELECT IFNULL(SUM(INDEX_LENGTH),0) from information_schema.TABLES where ENGINE='MyISAM' */") - - if [ -z $myisam_indexes ] ; then - myisam_indexes=$(find $datadir -name '*.MYI' -exec du $duflags '{}' \; 2>&1 | awk '{ s += $1 } END { printf("%.0f\n", s )}') - fi - - if [ $key_reads -eq 0 ] ; then - cecho "No key reads. If you aren't using MyISAM, this is normal. If you are" yellow - cecho "using MyISAM, this is very, very bad." yellow - key_cache_miss_rate=0 - key_buffer_free=$(echo "$key_blocks_unused * $key_cache_block_size / $key_buffer_size * 100" | bc -l ) - key_buffer_freeRND=$(echo "scale=0; $key_buffer_free / 1" | bc -l) - else - key_cache_miss_rate=$(($key_read_requests/$key_reads)) - if [ ! -z $key_blocks_unused ] ; then - key_buffer_free=$(echo "$key_blocks_unused * $key_cache_block_size / $key_buffer_size * 100" | bc -l ) - key_buffer_freeRND=$(echo "scale=0; $key_buffer_free / 1" | bc -l) - else - key_buffer_free='Unknown' - key_buffer_freeRND=75 - fi - fi - - human_readable $myisam_indexes myisam_indexesHR - cecho "Current MyISAM index space = $myisam_indexesHR $unit" - - human_readable $key_buffer_size key_buffer_sizeHR - cecho "Current key_buffer_size = $key_buffer_sizeHR $unit" - cecho "Key cache miss rate is 1 : $key_cache_miss_rate" - cecho "Key buffer free ratio = $key_buffer_freeRND %" - - if [ "$major_version" = '5.1' ] && [ $mysql_version_num -lt 050123 ] ; then - if [ $key_buffer_size -ge 4294967296 ] && ( echo "x86_64 ppc64 ia64 sparc64 i686" | grep -q $mysql_version_compile_machine ) ; then - cecho "Using key_buffer_size > 4GB will cause instability in versions prior to 5.1.23 " boldred - cecho "See Bug#5731, Bug#29419, Bug#29446" boldred - fi - fi - if [ "$major_version" = '5.0' ] && [ $mysql_version_num -lt 050052 ] ; then - if [ $key_buffer_size -ge 4294967296 ] && ( echo "x86_64 ppc64 ia64 sparc64 i686" | grep -q $mysql_version_compile_machine ) ; then - cecho "Using key_buffer_size > 4GB will cause instability in versions prior to 5.0.52 " boldred - cecho "See Bug#5731, Bug#29419, Bug#29446" boldred - fi - fi - if [ "$major_version" = '4.1' -o "$major_version" = '4.0' ] && [ $key_buffer_size -ge 4294967296 ] && ( echo "x86_64 ppc64 ia64 sparc64 i686" | grep -q $mysql_version_compile_machine ) ; then - cecho "Using key_buffer_size > 4GB will cause instability in versions prior to 5.0.52 " boldred - cecho "Reduce key_buffer_size to a safe value" boldred - cecho "See Bug#5731, Bug#29419, Bug#29446" boldred - fi - - if [ $key_cache_miss_rate -le 100 ] && [ $key_cache_miss_rate -gt 0 ] && [ $key_buffer_freeRND -le 20 ]; then - cecho "You could increase key_buffer_size" boldred - cecho "It is safe to raise this up to 1/4 of total system memory;" - cecho "assuming this is a dedicated database server." - elif [ $key_buffer_freeRND -le 20 ] && [ $key_buffer_size -le $myisam_indexes ] ; then - cecho "You could increase key_buffer_size" boldred - cecho "It is safe to raise this up to 1/4 of total system memory;" - cecho "assuming this is a dedicated database server." - elif [ $key_cache_miss_rate -ge 10000 ] || [ $key_buffer_freeRND -le 50 ] ; then - cecho "Your key_buffer_size seems to be too high." red - cecho "Perhaps you can use these resources elsewhere" red - else - cecho "Your key_buffer_size seems to be fine" green - fi -} - -check_query_cache () { - cecho "QUERY CACHE" boldblue - - mysql_variable \'version\' mysql_version - mysql_variable \'query_cache_size\' query_cache_size - mysql_variable \'query_cache_limit\' query_cache_limit - mysql_variable \'query_cache_min_res_unit\' query_cache_min_res_unit - mysql_status \'Qcache_free_memory\' qcache_free_memory - mysql_status \'Qcache_total_blocks\' qcache_total_blocks - mysql_status \'Qcache_free_blocks\' qcache_free_blocks - mysql_status \'Qcache_lowmem_prunes\' qcache_lowmem_prunes - - if [ -z $query_cache_size ] ; then - cecho "Your server does not support the query cache. That's probably a good thing." green - elif [ $query_cache_size -eq 0 ] ; then - cecho "Query cache is supported, but not enabled." yellow - cecho "Determine if enabling cache is advisable given your load characteristics," yellow - cecho "daemon version, and SMP (multiprocessor) status." yellow - else - qcache_used_memory=$(($query_cache_size-$qcache_free_memory)) - qcache_mem_fill_ratio=$(echo "scale=2; $qcache_used_memory * 100 / $query_cache_size" | bc -l) - qcache_mem_fill_ratioHR=$(echo "scale=0; $qcache_mem_fill_ratio / 1" | bc -l) - - cecho "You have query cache enabled. With many versions of the server, you may see" yellow - cecho "query cache lock contention, especially if you have more than one core." yellow - human_readable $query_cache_size query_cache_sizeHR - cecho "Current query_cache_size = $query_cache_sizeHR $unit" - human_readable $qcache_used_memory qcache_used_memoryHR - cecho "Current query_cache_used = $qcache_used_memoryHR $unit" - human_readable $query_cache_limit query_cache_limitHR - cecho "Current query_cache_limit = $query_cache_limitHR $unit" - cecho "Current Query cache Memory fill ratio = $qcache_mem_fill_ratio %" - if [ -z $query_cache_min_res_unit ] ; then - cecho "No query_cache_min_res_unit is defined. Using MySQL < 4.1 cache fragmentation can be inpredictable" yellow - else - human_readable $query_cache_min_res_unit query_cache_min_res_unitHR - cecho "Current query_cache_min_res_unit = $query_cache_min_res_unitHR $unit" - fi - if [ $qcache_free_blocks -gt 2 ] && [ $qcache_total_blocks -gt 0 ] ; then - qcache_percent_fragmented=$(echo "scale=2; $qcache_free_blocks * 100 / $qcache_total_blocks" | bc -l) - qcache_percent_fragmentedHR=$(echo "scale=0; $qcache_percent_fragmented / 1" | bc -l) - if [ $qcache_percent_fragmentedHR -gt 20 ] ; then - cecho "Query Cache is $qcache_percent_fragmentedHR % fragmented" red - cecho "Run \"FLUSH QUERY CACHE\" periodically to defragment the query cache memory" red - cecho "If you have many small queries lower 'query_cache_min_res_unit' to reduce fragmentation." red - fi - fi - - if [ $qcache_mem_fill_ratioHR -le 25 ] ; then - cecho "Your query_cache_size seems to be too high." red - cecho "Perhaps you can use these resources elsewhere" red - fi - if [ $qcache_lowmem_prunes -ge 50 ] && [ $qcache_mem_fill_ratioHR -ge 80 ]; then - cechon "However, " - cechon "$qcache_lowmem_prunes " boldred - cecho "queries have been removed from the query cache due to lack of memory" - cecho "Perhaps you should raise query_cache_size" boldred - fi - cecho "MySQL won't cache query results that are larger than query_cache_limit in size" yellow - fi -} - -check_sort_operations () { - -## -- Sort Operations -- ## - - cecho "SORT OPERATIONS" boldblue - - mysql_status \'Sort_merge_passes\' sort_merge_passes - mysql_status \'Sort_scan\' sort_scan - mysql_status \'Sort_range\' sort_range - mysql_variable \'sort_buffer_size\' sort_buffer_size - mysql_variable \'read_rnd_buffer_size\' read_rnd_buffer_size - - total_sorts=$(($sort_scan+$sort_range)) - if [ -z $read_rnd_buffer_size ] ; then - mysql_variable \'record_buffer\' read_rnd_buffer_size - fi - - ## Correct for rounding error in mysqld where 512K != 524288 ## - sort_buffer_size=$(($sort_buffer_size+8)) - read_rnd_buffer_size=$(($read_rnd_buffer_size+8)) - - human_readable $sort_buffer_size sort_buffer_sizeHR - cecho "Current sort_buffer_size = $sort_buffer_sizeHR $unit" - - human_readable $read_rnd_buffer_size read_rnd_buffer_sizeHR - cechon "Current " - if [ "$major_version" = '3.23' ] ; then - cechon "record_rnd_buffer " - else - cechon "read_rnd_buffer_size " - fi - cecho "= $read_rnd_buffer_sizeHR $unit" - - if [ $total_sorts -eq 0 ] ; then - cecho "No sort operations have been performed" - passes_per_sort=0 - fi - if [ $sort_merge_passes -ne 0 ] ; then - passes_per_sort=$(($sort_merge_passes/$total_sorts)) - else - passes_per_sort=0 - fi - - if [ $passes_per_sort -ge 2 ] ; then - cechon "On average " - cechon "$passes_per_sort " boldred - cecho "sort merge passes are made per sort operation" - cecho "You should raise your sort_buffer_size" - cechon "You should also raise your " - if [ "$major_version" = '3.23' ] ; then - cecho "record_rnd_buffer_size" - else - cecho "read_rnd_buffer_size" - fi - else - cecho "Sort buffer seems to be fine" green - fi -} - -check_join_operations () { - -## -- Joins -- ## - - cecho "JOINS" boldblue - - mysql_status \'Select_full_join\' select_full_join - mysql_status \'Select_range_check\' select_range_check - mysql_variable \'join_buffer_size\' join_buffer_size - - ## Some 4K is dropped from join_buffer_size adding it back to make sane ## - ## handling of human-readable conversion ## - - join_buffer_size=$(($join_buffer_size+4096)) - - human_readable $join_buffer_size join_buffer_sizeHR 2 - - cecho "Current join_buffer_size = $join_buffer_sizeHR $unit" - cecho "You have had $select_full_join queries where a join could not use an index properly" - - if [ $select_range_check -eq 0 ] && [ $select_full_join -eq 0 ] ; then - cecho "Your joins seem to be using indexes properly" green - fi - if [ $select_full_join -gt 0 ] ; then - print_error='true' - raise_buffer='true' - fi - if [ $select_range_check -gt 0 ] ; then - cecho "You have had $select_range_check joins without keys that check for key usage after each row" red - print_error='true' - raise_buffer='true' - fi - - ## For Debugging ## - # print_error='true' - if [ $join_buffer_size -ge 4194304 ] ; then - cecho "join_buffer_size >= 4 M" boldred - cecho "This is not advised" boldred - raise_buffer= - fi - - if [ $print_error ] ; then - if [ "$major_version" = '3.23' ] || [ "$major_version" = '4.0' ] ; then - cecho "You should enable \"log-long-format\" " - elif [ "$mysql_version_num" -gt 040100 ]; then - cecho "You should enable \"log-queries-not-using-indexes\"" - fi - cecho "Then look for non indexed joins in the slow query log." - if [ $raise_buffer ] ; then - cecho "If you are unable to optimize your queries you may want to increase your" - cecho "join_buffer_size to accommodate larger joins in one pass." - printf "\n" - cecho "Note! This script will still suggest raising the join_buffer_size when" boldred - cecho "ANY joins not using indexes are found." boldred - fi - fi - - # XXX Add better tests for join_buffer_size pending mysql bug #15088 XXX # -} - -check_tmp_tables () { - -## -- Temp Tables -- ## - - cecho "TEMP TABLES" boldblue - - mysql_status \'Created_tmp_tables\' created_tmp_tables - mysql_status \'Created_tmp_disk_tables\' created_tmp_disk_tables - mysql_variable \'tmp_table_size\' tmp_table_size - mysql_variable \'max_heap_table_size\' max_heap_table_size - - - if [ $created_tmp_tables -eq 0 ] ; then - tmp_disk_tables=0 - else - tmp_disk_tables=$((created_tmp_disk_tables*100/(created_tmp_tables+created_tmp_disk_tables))) - fi - human_readable $max_heap_table_size max_heap_table_sizeHR - cecho "Current max_heap_table_size = $max_heap_table_sizeHR $unit" - - human_readable $tmp_table_size tmp_table_sizeHR - cecho "Current tmp_table_size = $tmp_table_sizeHR $unit" - - cecho "Of $created_tmp_tables temp tables, $tmp_disk_tables% were created on disk" - if [ $tmp_table_size -gt $max_heap_table_size ] ; then - cecho "Effective in-memory tmp_table_size is limited to max_heap_table_size." yellow - fi - if [ $tmp_disk_tables -ge 25 ] ; then - cecho "Perhaps you should increase your tmp_table_size and/or max_heap_table_size" boldred - cecho "to reduce the number of disk-based temporary tables" boldred - cecho "Note! BLOB and TEXT columns are not allowed in memory tables." yellow - cecho "If you are using these columns raising these values might not impact your " yellow - cecho "ratio of on disk temp tables." yellow - else - cecho "Created disk tmp tables ratio seems fine" green - fi -} - -check_open_files () { - -## -- Open Files Limit -- ## - cecho "OPEN FILES LIMIT" boldblue - - mysql_variable \'open_files_limit\' open_files_limit - mysql_status \'Open_files\' open_files - - if [ -z $open_files_limit ] || [ $open_files_limit -eq 0 ] ; then - open_files_limit=$(ulimit -n) - cant_override=1 - else - cant_override=0 - fi - cecho "Current open_files_limit = $open_files_limit files" - - open_files_ratio=$(($open_files*100/$open_files_limit)) - - cecho "The open_files_limit should typically be set to at least 2x-3x" yellow - cecho "that of table_cache if you have heavy MyISAM usage." yellow - if [ $open_files_ratio -ge 75 ] ; then - cecho "You currently have open more than 75% of your open_files_limit" boldred - if [ $cant_override -eq 1 ] ; then - cecho "You should set a higher value for ulimit -u in the mysql startup script then restart mysqld" boldred - cecho "MySQL 3.23 users : This is just a guess based upon the current shell's ulimit -u value" yellow - elif [ $cant_override -eq 0 ] ; then - cecho "You should set a higher value for open_files_limit in my.cnf" boldred - else - cecho "ERROR can't determine if mysqld override of ulimit is allowed" boldred - exit 1 - fi - else - cecho "Your open_files_limit value seems to be fine" green - fi - - - -} - -check_table_cache () { - -## -- Table Cache -- ## - - cecho "TABLE CACHE" boldblue - - mysql_variable \'datadir\' datadir - mysql_variable \'table_cache\' table_cache - - ## /* MySQL +5.1 version of table_cache */ ## - mysql_variable \'table_open_cache\' table_open_cache - mysql_variable \'table_definition_cache\' table_definition_cache - - mysql_status \'Open_tables\' open_tables - mysql_status \'Opened_tables\' opened_tables - mysql_status \'Open_table_definitions\' open_table_definitions - - table_count=$($MYSQL_COMMAND -Bse "/*!50000 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' */") - - if [ -z "$table_count" ] ; then - if [ "$UID" != "$socket_owner" ] && [ "$UID" != "0" ] ; then - cecho "You are not '$socket_owner' or 'root'" red - cecho "I am unable to determine the table_count!" red - else - table_count=$(find $datadir 2>&1 | grep -c .frm$) - fi - fi - if [ $table_open_cache ] ; then - table_cache=$table_open_cache - fi - - if [ $opened_tables -ne 0 ] && [ $table_cache -ne 0 ] ; then - table_cache_hit_rate=$(($open_tables*100/$opened_tables)) - table_cache_fill=$(($open_tables*100/$table_cache)) - elif [ $opened_tables -eq 0 ] && [ $table_cache -ne 0 ] ; then - table_cache_hit_rate=100 - table_cache_fill=$(($open_tables*100/$table_cache)) - else - cecho "ERROR no table_cache ?!" boldred - exit 1 - fi - if [ $table_cache ] && [ ! $table_open_cache ] ; then - cecho "Current table_cache value = $table_cache tables" - fi - if [ $table_open_cache ] ; then - cecho "Current table_open_cache = $table_open_cache tables" - cecho "Current table_definition_cache = $table_definition_cache tables" - fi - if [ $table_count ] ; then - cecho "You have a total of $table_count tables" - fi - - if [ $table_cache_fill -lt 95 ] ; then - cechon "You have " - cechon "$open_tables " green - cecho "open tables." - cecho "The table_cache value seems to be fine" green - elif [ $table_cache_hit_rate -le 85 -o $table_cache_fill -ge 95 ]; then - cechon "You have " - cechon "$open_tables " boldred - cecho "open tables." - cechon "Current table_cache hit rate is " - cecho "$table_cache_hit_rate%" boldred - cechon ", while " - cechon "$table_cache_fill% " boldred - cecho "of your table cache is in use" - cecho "You should probably increase your table_cache" red - else - cechon "Current table_cache hit rate is " - cechon "$table_cache_hit_rate%" green - cechon ", while " - cechon "$table_cache_fill% " green - cecho "of your table cache is in use" - cecho "The table cache value seems to be fine" green - fi - if [ $table_definition_cache ] && [ $table_definition_cache -le $table_count ] && [ $table_count -ge 100 ] ; then - cecho "You should probably increase your table_definition_cache value." red - fi -} - -check_table_locking () { - -## -- Table Locking -- ## - - cecho "TABLE LOCKING" boldblue - - mysql_status \'Table_locks_waited\' table_locks_waited - mysql_status \'Table_locks_immediate\' table_locks_immediate - mysql_variable \'concurrent_insert\' concurrent_insert - mysql_variable \'low_priority_updates\' low_priority_updates - if [ "$concurrent_insert" = 'ON' ]; then - concurrent_insert=1 - elif [ "$concurrent_insert" = 'OFF' ]; then - concurrent_insert=0 - fi - - cechon "Current Lock Wait ratio = " - if [ $table_locks_waited -gt 0 ]; then - immediate_locks_miss_rate=$(($table_locks_immediate/$table_locks_waited)) - cecho "1 : $immediate_locks_miss_rate" red - else - immediate_locks_miss_rate=99999 # perfect - cecho "0 : $questions" - fi - if [ $immediate_locks_miss_rate -lt 5000 ] ; then - cecho "You may benefit from selective use of InnoDB." - if [ "$low_priority_updates" = 'OFF' ] ; then - cecho "If you have long-running SELECT's against MyISAM tables and perform" - cecho "frequent updates consider setting 'low_priority_updates=1'" - fi - if [ "$mysql_version_num" -gt 050000 ] && [ "$mysql_version_num" -lt 050500 ]; then - if [ $concurrent_insert -le 1 ] ; then - cecho "If you have a high concurrency of inserts on Dynamic row-length tables" - cecho "consider setting 'concurrent_insert=2'." - fi - elif [ "$mysql_version_num" -gt 050500 ] ; then - if [ "$concurrent_insert" = 'AUTO' ] || [ "$concurrent_insert" = 'NEVER' ] ; then - cecho "If you have a high concurrency of inserts on Dynamic row-length tables" - cecho "consider setting 'concurrent_insert=ALWAYS'." - fi - fi - else - cecho "Your table locking seems to be fine" green - fi -} - -check_table_scans () { - -## -- Table Scans -- ## - - cecho "TABLE SCANS" boldblue - - mysql_status \'Com_select\' com_select - mysql_status \'Handler_read_rnd_next\' read_rnd_next - mysql_variable \'read_buffer_size\' read_buffer_size - - if [ -z $read_buffer_size ] ; then - mysql_variable \'record_buffer\' read_buffer_size - fi - - human_readable $read_buffer_size read_buffer_sizeHR - cecho "Current read_buffer_size = $read_buffer_sizeHR $unit" - - if [ $com_select -gt 0 ] ; then - full_table_scans=$(($read_rnd_next/$com_select)) - cecho "Current table scan ratio = $full_table_scans : 1" - if [ $full_table_scans -ge 4000 ] && [ $read_buffer_size -le 2097152 ] ; then - cecho "You have a high ratio of sequential access requests to SELECTs" red - cechon "You may benefit from raising " red - if [ "$major_version" = '3.23' ] ; then - cechon "record_buffer " red - else - cechon "read_buffer_size " red - fi - cecho "and/or improving your use of indexes." red - elif [ $read_buffer_size -gt 8388608 ] ; then - cechon "read_buffer_size is over 8 MB " red - cecho "there is probably no need for such a large read_buffer" red - - else - cecho "read_buffer_size seems to be fine" green - fi - else - cecho "read_buffer_size seems to be fine" green - fi -} - -function check_innodb_status() -{ - ## See http://bugs.mysql.com/59393 - - mysql_variable \'have_innodb\' have_innodb - - if [ "$mysql_version_num" -lt 050500 ] && [ "$have_innodb" = "YES" ] ; then - innodb_enabled=1 - fi - - if [ "${mysql_version//Maria}" != "${mysql_version}" ] || \ - [ "${mysql_version_num}" -ge 050700 ]; then - # In MariaDB and MySQL >=5.7, InnoDB is always present, excepting Rocks and the like. - mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb - # MariaDB gives you a lovely option for disabling InnoDB that doesn't _technically_ - # necessitate setting ignore_builtin_innodb. Thanks for that. Very cute. See - # https://github.com/BMDan/tuning-primer.sh/issues/12 . - if [ "$ignore_builtin_innodb" = "ON" ] || [ "$have_innodb" = "DISABLED" ] || [ "$have_innodb" = "NO" ]; then - innodb_enabled=0 - else - innodb_enabled=1 - fi - elif [ "$mysql_version_num" -ge 050500 ] && [ "$mysql_version_num" -lt 050512 ] ; then - mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb - if [ "$ignore_builtin_innodb" = "ON" ] || [ $have_innodb = "NO" ] ; then - innodb_enabled=0 - else - innodb_enabled=1 - fi - elif [ "$major_version" = '5.5' ] && [ "$mysql_version_num" -ge 050512 ] ; then - mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb - if [ "$ignore_builtin_innodb" = "ON" ] ; then - innodb_enabled=0 - else - innodb_enabled=1 - fi - elif [ "$mysql_version_num" -ge 050600 ] && [ "$mysql_version_num" -lt 050603 ] ; then - mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb - if [ "$ignore_builtin_innodb" = "ON" ] || [ $have_innodb = "NO" ] ; then - innodb_enabled=0 - else - innodb_enabled=1 - fi - elif [ "$major_version" = '5.6' ] && [ "$mysql_version_num" -ge 050603 ] ; then - mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb - if [ "$ignore_builtin_innodb" = "ON" ] ; then - innodb_enabled=0 - else - innodb_enabled=1 - fi - fi - - if [ "$innodb_enabled" = 1 ] ; then - mysql_variable \'innodb_buffer_pool_size\' innodb_buffer_pool_size - mysql_variable \'innodb_additional_mem_pool_size\' innodb_additional_mem_pool_size - mysql_variable \'innodb_fast_shutdown\' innodb_fast_shutdown - mysql_variable \'innodb_flush_log_at_trx_commit\' innodb_flush_log_at_trx_commit - mysql_variable \'innodb_locks_unsafe_for_binlog\' innodb_locks_unsafe_for_binlog - mysql_variable \'innodb_log_buffer_size\' innodb_log_buffer_size - mysql_variable \'innodb_log_file_size\' innodb_log_file_size - mysql_variable \'innodb_log_files_in_group\' innodb_log_files_in_group - mysql_variable \'innodb_safe_binlog\' innodb_safe_binlog - mysql_variable \'innodb_thread_concurrency\' innodb_thread_concurrency - - cecho "INNODB STATUS" boldblue - innodb_indexes=$($MYSQL_COMMAND -Bse "/*!50000 SELECT IFNULL(SUM(INDEX_LENGTH),0) from information_schema.TABLES where ENGINE='InnoDB' */") - innodb_data=$($MYSQL_COMMAND -Bse "/*!50000 SELECT IFNULL(SUM(DATA_LENGTH),0) from information_schema.TABLES where ENGINE='InnoDB' */") - - if [ ! -z "$innodb_indexes" ] ; then - mysql_status \'Innodb_buffer_pool_pages_data\' innodb_buffer_pool_pages_data - mysql_status \'Innodb_buffer_pool_pages_misc\' innodb_buffer_pool_pages_misc - mysql_status \'Innodb_buffer_pool_pages_free\' innodb_buffer_pool_pages_free - mysql_status \'Innodb_buffer_pool_pages_total\' innodb_buffer_pool_pages_total - - mysql_status \'Innodb_buffer_pool_read_ahead_seq\' innodb_buffer_pool_read_ahead_seq - mysql_status \'Innodb_buffer_pool_read_requests\' innodb_buffer_pool_read_requests - - mysql_status \'Innodb_os_log_pending_fsyncs\' innodb_os_log_pending_fsyncs - mysql_status \'Innodb_os_log_pending_writes\' innodb_os_log_pending_writes - mysql_status \'Innodb_log_waits\' innodb_log_waits - - mysql_status \'Innodb_row_lock_time\' innodb_row_lock_time - mysql_status \'Innodb_row_lock_waits\' innodb_row_lock_waits - - human_readable $innodb_indexes innodb_indexesHR - cecho "Current InnoDB index space = $innodb_indexesHR $unit" - human_readable $innodb_data innodb_dataHR - cecho "Current InnoDB data space = $innodb_dataHR $unit" - percent_innodb_buffer_pool_free=$(($innodb_buffer_pool_pages_free*100/$innodb_buffer_pool_pages_total)) - cecho "Current InnoDB buffer pool free = ${percent_innodb_buffer_pool_free} %" - - else - cecho "Cannot parse InnoDB stats prior to 5.0.x" red - $MYSQL_COMMAND -s -e "SHOW /*!50000 ENGINE */ INNODB STATUS\G" - fi - - human_readable $innodb_buffer_pool_size innodb_buffer_pool_sizeHR - cecho "Current innodb_buffer_pool_size = $innodb_buffer_pool_sizeHR $unit" - cecho "Depending on how much space your innodb indexes take up it may be safe" - cecho "to increase this value to up to 2 / 3 of total system memory" - else - cecho "No InnoDB Support Enabled!" boldred - fi -} - -total_memory_used () { - -## -- Total Memory Usage -- ## - cecho "MEMORY USAGE" boldblue - - mysql_variable \'read_buffer_size\' read_buffer_size - mysql_variable \'read_rnd_buffer_size\' read_rnd_buffer_size - mysql_variable \'sort_buffer_size\' sort_buffer_size - mysql_variable \'thread_stack\' thread_stack - mysql_variable \'max_connections\' max_connections - mysql_variable \'join_buffer_size\' join_buffer_size - mysql_variable \'tmp_table_size\' tmp_table_size - mysql_variable \'max_heap_table_size\' max_heap_table_size - mysql_variable \'log_bin\' log_bin - mysql_status \'Max_used_connections\' max_used_connections - - if [ "$major_version" = "3.23" ] ; then - mysql_variable \'record_buffer\' read_buffer_size - mysql_variable \'record_rnd_buffer\' read_rnd_buffer_size - mysql_variable \'sort_buffer\' sort_buffer_size - fi - - if [ "$log_bin" = "ON" ] ; then - mysql_variable \'binlog_cache_size\' binlog_cache_size - else - binlog_cache_size=0 - fi - - if [ $max_heap_table_size -le $tmp_table_size ] ; then - effective_tmp_table_size=$max_heap_table_size - else - effective_tmp_table_size=$tmp_table_size - fi - - - per_thread_buffers=$(echo "($read_buffer_size+$read_rnd_buffer_size+$sort_buffer_size+$thread_stack+$join_buffer_size+$binlog_cache_size)*$max_connections" | bc -l) - per_thread_max_buffers=$(echo "($read_buffer_size+$read_rnd_buffer_size+$sort_buffer_size+$thread_stack+$join_buffer_size+$binlog_cache_size)*$max_used_connections" | bc -l) - - mysql_variable \'innodb_buffer_pool_size\' innodb_buffer_pool_size - if [ -z $innodb_buffer_pool_size ] ; then - innodb_buffer_pool_size=0 - fi - - mysql_variable \'innodb_additional_mem_pool_size\' innodb_additional_mem_pool_size - if [ -z $innodb_additional_mem_pool_size ] ; then - innodb_additional_mem_pool_size=0 - fi - - mysql_variable \'innodb_log_buffer_size\' innodb_log_buffer_size - if [ -z $innodb_log_buffer_size ] ; then - innodb_log_buffer_size=0 - fi - - mysql_variable \'key_buffer_size\' key_buffer_size - - mysql_variable \'query_cache_size\' query_cache_size - if [ -z $query_cache_size ] ; then - query_cache_size=0 - fi - - global_buffers=$(echo "$innodb_buffer_pool_size+$innodb_additional_mem_pool_size+$innodb_log_buffer_size+$key_buffer_size+$query_cache_size" | bc -l) - - - max_memory=$(echo "$global_buffers+$per_thread_max_buffers" | bc -l) - total_memory=$(echo "$global_buffers+$per_thread_buffers" | bc -l) - - pct_of_sys_mem=$(echo "scale=0; $total_memory*100/$physical_memory" | bc -l) - - if [ $pct_of_sys_mem -gt 90 ] ; then - txt_color=boldred - error=1 - else - txt_color= - error=0 - fi - - human_readable $max_memory max_memoryHR - cecho "Max Memory Ever Allocated : $max_memoryHR $unit" $txt_color - human_readable $per_thread_buffers per_thread_buffersHR - cecho "Configured Max Per-thread Buffers : $per_thread_buffersHR $unit" $txt_color - human_readable $global_buffers global_buffersHR - cecho "Configured Max Global Buffers : $global_buffersHR $unit" $txt_color - human_readable $total_memory total_memoryHR - cecho "Configured Max Memory Limit : $total_memoryHR $unit" $txt_color -# human_readable $effective_tmp_table_size effective_tmp_table_sizeHR -# cecho "Plus $effective_tmp_table_sizeHR $unit per temporary table created" - human_readable $physical_memory physical_memoryHR - cecho "Physical Memory : $physical_memoryHR $unit" $txt_color - if [ $error -eq 1 ] ; then - printf "\n" - cecho "Max memory limit exceeds 90% of physical memory" $txt_color - else - cecho "Max memory limit seem to be within acceptable norms" green - fi - unset txt_color -} - -## Required Functions ## - -login_validation () { - export MYSQL_COMMAND="mysql" - export MYSQLADMIN_COMMAND="mysqladmin" - - if [ -n "${socket-}" ]; then - # First, we look for a socket. Then, we try to find old Plesk - # login creds (is this still needed?). Then, we try the truly - # revolutionary approach of just trying to connect and seeing - # what happens. - check_for_socket || - check_for_plesk_passwords || - check_mysql_login || - exit 1 - else - # If the user didn't specify a socket for us (and they usually - # won't), let's try to just connect and see if that works. If - # it doesn't, try to guess at a socket path, and then break out - # the ol' Plesk trick if things get really desperate. - check_mysql_login || - check_for_socket || - check_for_plesk_passwords || - exit 1 - fi - export major_version=$($MYSQL_COMMAND -Bse "SELECT SUBSTRING_INDEX(VERSION(), '.', +2)") -# export mysql_version_num=$($MYSQL_COMMAND -Bse "SELECT LEFT(REPLACE(SUBSTRING_INDEX(VERSION(), '-', +1), '.', ''),4)" ) - export mysql_version_num=$($MYSQL_COMMAND -Bse "SELECT VERSION()" | - awk -F \. '{ printf "%02d", $1; printf "%02d", $2; printf "%02d", $3 }') - -} - -shared_info () { - export major_version=$($MYSQL_COMMAND -Bse "SELECT SUBSTRING_INDEX(VERSION(), '.', +2)") - # export mysql_version_num=$($MYSQL_COMMAND -Bse "SELECT LEFT(REPLACE(SUBSTRING_INDEX(VERSION(), '-', +1), '.', ''),4)" ) - export mysql_version_num=$($MYSQL_COMMAND -Bse "SELECT VERSION()" | - awk -F \. '{ printf "%02d", $1; printf "%02d", $2; printf "%02d", $3 }') - mysql_status \'Questions\' questions -# socket_owner=$(find -L $socket -printf '%u\n') - socket_owner=$(ls -nH $socket | awk '{ print $3 }') -} - - -get_system_info () { - - export OS=$(uname) - - # Get information for various UNIXes - if [ "$OS" = 'Darwin' ]; then - ps_socket=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }' | head -1) - found_socks=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }') - export physical_memory=$(sysctl -n hw.memsize) - export duflags='' - elif [ "$OS" = 'FreeBSD' ] || [ "$OS" = 'OpenBSD' ]; then - ## On FreeBSD must be root to locate sockets. - ps_socket=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }' | head -1) - found_socks=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }') - export physical_memory=$(sysctl -n hw.realmem) - export duflags='' - elif [ "$OS" = 'Linux' ] ; then - ## Includes SWAP - ## export physical_memory=$(free -b | grep -v buffers | awk '{ s += $2 } END { printf("%.0f\n", s ) }') - if command -v netstat 2>/dev/null >/dev/null; then - ps_socket=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }' | head -1) - found_socks=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }') - else - ps_socket=$(ss -ln 2>/dev/null | awk '/mysql(.*)?\.sock/ { print $5; exit }') - ps_socket=$(ss -ln 2>/dev/null | awk '/mysql(.*)?\.sock/ { print $5 }') - fi - export physical_memory=$(awk '/^MemTotal/ { printf("%.0f", $2*1024 ) }' < /proc/meminfo) - export duflags='-b' - elif [ "$OS" = 'SunOS' ] ; then - ps_socket=$(netstat -an | awk '/mysql(.*)?.sock/ { print $5 }' | head -1) - found_socks=$(netstat -an | awk '/mysql(.*)?.sock/ { print $5 }') - export physical_memory=$(prtconf | awk '/^Memory\ size:/ { print $3*1048576 }') - fi - if [ -z "$(which bc)" ] ; then - echo "Error: Command line calculator 'bc' not found!" - exit - fi -} - - -## Optional Components Groups ## - -banner_info () { - shared_info - print_banner ; echo - check_mysql_version ; echo - post_uptime_warning ; echo -} - -misc () { - shared_info - check_slow_queries ; echo - check_binary_log ; echo - check_threads ; echo - check_used_connections ; echo - check_innodb_status ; echo -} - -memory () { - shared_info - total_memory_used ; echo - check_key_buffer_size ; echo - check_query_cache ; echo - check_sort_operations ; echo - check_join_operations ; echo -} - -file () { - shared_info - check_open_files ; echo - check_table_cache ; echo - check_tmp_tables ; echo - check_table_scans ; echo - check_table_locking ; echo -} - -all () { - banner_info - misc - memory - file -} - -prompt () { - prompted='true' - read -p "Username [anonymous] : " user - read -rsp "Password [] : " pass - cecho " " - read -p "Socket [ /var/lib/mysql/mysql.sock ] : " socket - if [ -z $socket ] ; then - export socket='/var/lib/mysql/mysql.sock' - fi - - local tempmycnf - tempmycnf="$(mktemp)" - write_mycnf "$tempmycnf" "$socket" "$user" "$pass" - - export MYSQL_COMMAND="mysql --defaults-extra-file=$tempmycnf -u$user" - export MYSQLADMIN_COMMAND="mysqladmin --defaults-extra-file=$tempmycnf -u$user" - - check_for_socket || \ - check_mysql_login - - if [ $? = 1 ] ; then - exit 1 - fi - read -p "Mode to test - banner, file, misc, mem, innodb, [all] : " REPLY - if [ -z $REPLY ] ; then - REPLY='all' - fi - case $REPLY in - banner | BANNER | header | HEADER | head | HEAD) - banner_info - ;; - misc | MISC | miscellaneous ) - misc - ;; - mem | memory | MEM | MEMORY ) - memory - ;; - file | FILE | disk | DISK ) - file - ;; - innodb | INNODB ) - innodb - ;; - all | ALL ) - cecho " " - all - ;; - * ) - cecho "Invalid Mode! Valid options are 'banner', 'misc', 'memory', 'file', 'innodb' or 'all'" boldred - exit 1 - ;; - esac -} - -## Address environmental differences ## -get_system_info -# echo $ps_socket - -mode="$1" -if [ -z "${1-}" ] ; then - login_validation - mode='ALL' -elif [ "$1" != "prompt" ] && [ "$1" != "PROMPT" ] ; then - login_validation -fi - -case $mode in - all | ALL ) - cecho " " - all - ;; - mem | memory | MEM | MEMORY ) - cecho " " - memory - ;; - file | FILE | disk | DISK ) - cecho " " - file - ;; - banner | BANNER | header | HEADER | head | HEAD ) - banner_info - ;; - misc | MISC | miscellaneous ) - cecho " " - misc - ;; - innodb | INNODB ) - banner_info - check_innodb_status ; echo - ;; - prompt | PROMPT ) - prompt - ;; - debug | DEBUG ) - # Run everything, then dump state. - all >/dev/null - env - ;; - *) - cecho "usage: $0 [ all | banner | file | innodb | memory | misc | prompt ]" boldred - exit 1 - ;; -esac