@@ -399,45 +399,49 @@ function checkServices(ctx: CheckContext): CheckResult[] {
399399 return results ;
400400 }
401401
402- // Check Nginx
403- const nginxResult = vagrantSshSync ( "sudo service nginx status 2>&1" , ctx . vvvPath ) ;
404- if ( nginxResult . stdout ?. includes ( "is running" ) ) {
402+ // Batch all service checks into a single SSH call
403+ const batchCommand = `
404+ echo "===NGINX_START===" && sudo service nginx status 2>&1 && echo "===NGINX_OK===" || echo "===NGINX_FAIL==="
405+ echo "===MYSQL_START===" && mysqladmin ping 2>&1 && echo "===MYSQL_OK===" || echo "===MYSQL_FAIL==="
406+ echo "===PHP_START===" && (sudo service php8.2-fpm status 2>&1 || sudo service php8.1-fpm status 2>&1 || sudo service php8.0-fpm status 2>&1) && echo "===PHP_OK===" || echo "===PHP_FAIL==="
407+ echo "===MEMCACHED_START===" && sudo service memcached status 2>&1 && echo "===MEMCACHED_OK===" || echo "===MEMCACHED_FAIL==="
408+ echo "===MEMCACHED_PGREP===" && (pgrep -x memcached >/dev/null && echo running || echo stopped)
409+ ` ;
410+
411+ const result = vagrantSshSync ( batchCommand , ctx . vvvPath ) ;
412+ const output = result . stdout || "" ;
413+
414+ // Parse Nginx
415+ if ( output . includes ( "===NGINX_OK===" ) ) {
405416 results . push ( pass ( "Nginx" , category , "Nginx is running" ) ) ;
406417 } else {
407418 results . push ( fail ( "Nginx" , category , "Nginx is not running" , "Run: vvvlocal service start nginx" ) ) ;
408419 }
409420
410- // Check MariaDB
411- const mariaResult = vagrantSshSync ( "mysqladmin ping 2>&1" , ctx . vvvPath ) ;
412- if ( mariaResult . stdout ?. includes ( "alive" ) ) {
421+ // Parse MariaDB
422+ if ( output . includes ( "===MYSQL_OK===" ) ) {
413423 results . push ( pass ( "MariaDB" , category , "MariaDB is running" ) ) ;
414424 } else {
415425 results . push ( fail ( "MariaDB" , category , "MariaDB is not running" , "Run: vvvlocal service start mariadb" ) ) ;
416426 }
417427
418- // Check PHP-FPM (at least one version)
419- const phpResult = vagrantSshSync ( "sudo service php8.2-fpm status 2>&1 || sudo service php8.1-fpm status 2>&1 || sudo service php8.0-fpm status 2>&1" , ctx . vvvPath ) ;
420- if ( phpResult . stdout ?. includes ( "is running" ) ) {
428+ // Parse PHP-FPM
429+ if ( output . includes ( "===PHP_OK===" ) ) {
421430 results . push ( pass ( "PHP-FPM" , category , "PHP-FPM is running" ) ) ;
422431 } else {
423432 results . push ( fail ( "PHP-FPM" , category , "No PHP-FPM service running" , "Run: vvvlocal service start php" ) ) ;
424433 }
425434
426- // Check Memcached (warn only)
427- // Uses pgrep fallback since service script may not detect manually started processes
428- const memcachedResult = vagrantSshSync ( "sudo service memcached status 2>&1" , ctx . vvvPath ) ;
429- if ( memcachedResult . stdout ?. includes ( "is running" ) ) {
435+ // Parse Memcached
436+ const memcachedSection = output . substring ( output . indexOf ( "===MEMCACHED_START===" ) ) ;
437+ if ( output . includes ( "===MEMCACHED_OK===" ) ) {
430438 results . push ( pass ( "Memcached" , category , "Memcached is running" ) ) ;
431- } else if ( memcachedResult . stdout ? .includes ( "unrecognized service" ) ) {
439+ } else if ( memcachedSection . includes ( "unrecognized service" ) ) {
432440 results . push ( skip ( "Memcached" , category , "Memcached not installed" ) ) ;
441+ } else if ( memcachedSection . includes ( "===MEMCACHED_PGREP===" ) && memcachedSection . includes ( "running" ) ) {
442+ results . push ( pass ( "Memcached" , category , "Memcached is running" ) ) ;
433443 } else {
434- // Fallback: check if process is actually running
435- const pgrepResult = vagrantSshSync ( "pgrep -x memcached >/dev/null && echo running || echo stopped" , ctx . vvvPath ) ;
436- if ( pgrepResult . stdout ?. includes ( "running" ) ) {
437- results . push ( pass ( "Memcached" , category , "Memcached is running" ) ) ;
438- } else {
439- results . push ( warn ( "Memcached" , category , "Memcached is not running (optional)" , "Run: vvvlocal service start memcached" ) ) ;
440- }
444+ results . push ( warn ( "Memcached" , category , "Memcached is not running (optional)" , "Run: vvvlocal service start memcached" ) ) ;
441445 }
442446
443447 return results ;
@@ -535,19 +539,25 @@ function checkDatabase(ctx: CheckContext): CheckResult[] {
535539 return results ;
536540 }
537541
538- // Check MySQL connection
539- const connResult = vagrantSshSync ( "mysql -e 'SELECT 1' 2>&1" , ctx . vvvPath ) ;
540- if ( connResult . status === 0 ) {
542+ // Batch both database checks into a single SSH call
543+ const batchCommand = `
544+ echo "===MYSQL_CONN_START===" && mysql -e 'SELECT 1' 2>&1 && echo "===MYSQL_CONN_OK===" || echo "===MYSQL_CONN_FAIL==="
545+ echo "===MYSQL_DBS_START===" && mysql -e 'SHOW DATABASES' 2>&1
546+ ` ;
547+
548+ const result = vagrantSshSync ( batchCommand , ctx . vvvPath ) ;
549+ const output = result . stdout || "" ;
550+
551+ // Parse MySQL connection
552+ if ( output . includes ( "===MYSQL_CONN_OK===" ) ) {
541553 results . push ( pass ( "MySQL connection" , category , "Can connect to MySQL" ) ) ;
542554 } else {
543555 results . push ( fail ( "MySQL connection" , category , "Cannot connect to MySQL" , "Check MariaDB service" ) ) ;
544556 return results ;
545557 }
546558
547- // Check system databases exist
548- const dbResult = vagrantSshSync ( "mysql -e 'SHOW DATABASES' 2>&1" , ctx . vvvPath ) ;
549- const dbs = dbResult . stdout || "" ;
550- if ( dbs . includes ( "mysql" ) && dbs . includes ( "information_schema" ) ) {
559+ // Parse system databases
560+ if ( output . includes ( "mysql" ) && output . includes ( "information_schema" ) ) {
551561 results . push ( pass ( "System databases" , category , "System databases present" ) ) ;
552562 } else {
553563 results . push ( fail ( "System databases" , category , "System databases missing" , "MySQL installation may be corrupted" ) ) ;
@@ -626,38 +636,51 @@ function checkLogFiles(ctx: CheckContext): CheckResult[] {
626636 return results ;
627637 }
628638
629- // Check log sizes
630- const checkLog = ( name : string , path : string , maxMB : number ) => {
631- const result = vagrantSshSync ( `du -k ${ path } 2>/dev/null | cut -f1` , ctx . vvvPath ) ;
632- const sizeKB = parseInt ( result . stdout ?. trim ( ) || "0" , 10 ) ;
639+ // Batch all log and disk checks into a single SSH call
640+ const batchCommand = `
641+ echo "===NGINX_LOG===" && du -k /var/log/nginx/error.log 2>/dev/null | cut -f1 || echo "0"
642+ echo "===MYSQL_LOG===" && du -k /var/log/mysql/error.log 2>/dev/null | cut -f1 || echo "0"
643+ echo "===DISK_USAGE===" && df -h / | tail -1 | awk '{print $5}'
644+ ` ;
645+
646+ const result = vagrantSshSync ( batchCommand , ctx . vvvPath ) ;
647+ const output = result . stdout || "" ;
648+
649+ // Parse Nginx log size
650+ const nginxMatch = output . match ( / = = = N G I N X _ L O G = = = \s * ( \d + ) / ) ;
651+ if ( nginxMatch && nginxMatch [ 1 ] ) {
652+ const sizeKB = parseInt ( nginxMatch [ 1 ] , 10 ) ;
633653 const sizeMB = sizeKB / 1024 ;
654+ if ( sizeKB > 0 && sizeMB > 100 ) {
655+ results . push ( warn ( "Nginx error log" , category , `Nginx error log is ${ sizeMB . toFixed ( 0 ) } MB` , "Consider rotating logs in /var/log/nginx/error.log" ) ) ;
656+ } else if ( sizeKB > 0 ) {
657+ verbose ( `Nginx error log: ${ sizeMB . toFixed ( 1 ) } MB` ) ;
658+ }
659+ }
634660
635- if ( sizeKB === 0 ) {
636- // File doesn't exist or is empty - that's fine
637- return ;
661+ // Parse MySQL log size
662+ const mysqlMatch = output . match ( / = = = M Y S Q L _ L O G = = = \s * ( \d + ) / ) ;
663+ if ( mysqlMatch && mysqlMatch [ 1 ] ) {
664+ const sizeKB = parseInt ( mysqlMatch [ 1 ] , 10 ) ;
665+ const sizeMB = sizeKB / 1024 ;
666+ if ( sizeKB > 0 && sizeMB > 100 ) {
667+ results . push ( warn ( "MySQL error log" , category , `MySQL error log is ${ sizeMB . toFixed ( 0 ) } MB` , "Consider rotating logs in /var/log/mysql/error.log" ) ) ;
668+ } else if ( sizeKB > 0 ) {
669+ verbose ( `MySQL error log: ${ sizeMB . toFixed ( 1 ) } MB` ) ;
638670 }
671+ }
639672
640- if ( sizeMB > maxMB ) {
641- results . push ( warn ( `${ name } log` , category , `${ name } log is ${ sizeMB . toFixed ( 0 ) } MB` , `Consider rotating logs in ${ path } ` ) ) ;
673+ // Parse disk usage
674+ const diskMatch = output . match ( / = = = D I S K _ U S A G E = = = \s * ( \d + ) % / ) ;
675+ if ( diskMatch && diskMatch [ 1 ] ) {
676+ const usage = parseInt ( diskMatch [ 1 ] , 10 ) ;
677+ if ( usage >= 90 ) {
678+ results . push ( warn ( "VM disk usage" , category , `VM disk is ${ usage } % full` , "Free up space in the VM" ) ) ;
679+ } else if ( usage >= 80 ) {
680+ results . push ( warn ( "VM disk usage" , category , `VM disk is ${ usage } % full` ) ) ;
642681 } else {
643- verbose ( ` ${ name } log: ${ sizeMB . toFixed ( 1 ) } MB` ) ;
682+ results . push ( pass ( "VM disk usage" , category , `VM disk is ${ usage } % used` ) ) ;
644683 }
645- } ;
646-
647- checkLog ( "Nginx error" , "/var/log/nginx/error.log" , 100 ) ;
648- checkLog ( "MySQL error" , "/var/log/mysql/error.log" , 100 ) ;
649-
650- // Check VM disk usage
651- const dfResult = vagrantSshSync ( "df -h / | tail -1 | awk '{print $5}'" , ctx . vvvPath ) ;
652- const usageStr = dfResult . stdout ?. trim ( ) . replace ( "%" , "" ) || "0" ;
653- const usage = parseInt ( usageStr , 10 ) ;
654-
655- if ( usage >= 90 ) {
656- results . push ( warn ( "VM disk usage" , category , `VM disk is ${ usage } % full` , "Free up space in the VM" ) ) ;
657- } else if ( usage >= 80 ) {
658- results . push ( warn ( "VM disk usage" , category , `VM disk is ${ usage } % full` ) ) ;
659- } else {
660- results . push ( pass ( "VM disk usage" , category , `VM disk is ${ usage } % used` ) ) ;
661684 }
662685
663686 return results ;
@@ -670,18 +693,19 @@ function checkLogFiles(ctx: CheckContext): CheckResult[] {
670693/**
671694 * Show progress for a completed phase
672695 */
673- function showPhaseProgress ( phaseName : string , phaseResults : CheckResult [ ] ) : void {
696+ function showPhaseProgress ( phaseName : string , phaseResults : CheckResult [ ] , startTime : number ) : void {
674697 const passed = phaseResults . filter ( r => r . status === "pass" ) . length ;
675698 const failed = phaseResults . filter ( r => r . status === "fail" ) . length ;
676699 const warnings = phaseResults . filter ( r => r . status === "warn" ) . length ;
677700 const total = phaseResults . length ;
701+ const duration = ( ( Date . now ( ) - startTime ) / 1000 ) . toFixed ( 1 ) ;
678702
679703 if ( failed > 0 ) {
680- cli . error ( `✗ ${ phaseName } : ${ passed } /${ total } passed, ${ failed } failed${ warnings > 0 ? `, ${ warnings } warnings` : "" } ` ) ;
704+ cli . error ( `✗ ${ phaseName } : ${ passed } /${ total } passed, ${ failed } failed${ warnings > 0 ? `, ${ warnings } warnings` : "" } ( ${ duration } s) ` ) ;
681705 } else if ( warnings > 0 ) {
682- cli . warning ( `⚠ ${ phaseName } : ${ passed } /${ total } passed, ${ warnings } warnings` ) ;
706+ cli . warning ( `⚠ ${ phaseName } : ${ passed } /${ total } passed, ${ warnings } warnings ( ${ duration } s) ` ) ;
683707 } else {
684- cli . success ( `✓ ${ phaseName } : All ${ total } checks passed` ) ;
708+ cli . success ( `✓ ${ phaseName } : All ${ total } checks passed ( ${ duration } s) ` ) ;
685709 }
686710}
687711
@@ -694,34 +718,39 @@ async function runAllChecks(vvvPath: string): Promise<CheckResult[]> {
694718 } ;
695719
696720 const results : CheckResult [ ] = [ ] ;
721+ let phaseStart : number ;
697722
698723 // Phase 1: Prerequisites (can run without VM)
724+ phaseStart = Date . now ( ) ;
699725 cli . info ( "Checking prerequisites..." ) ;
700726 verbose ( "Checking prerequisites..." ) ;
701727 const prereqResults = await checkPrerequisites ( ctx ) ;
702728 results . push ( ...prereqResults ) ;
703- showPhaseProgress ( "Prerequisites" , prereqResults ) ;
729+ showPhaseProgress ( "Prerequisites" , prereqResults , phaseStart ) ;
704730
705731 // Phase 2: VVV Installation (can run without VM)
732+ phaseStart = Date . now ( ) ;
706733 cli . info ( "Checking VVV installation..." ) ;
707734 verbose ( "Checking VVV installation..." ) ;
708735 const vvvResults = await checkVvvInstallation ( ctx ) ;
709736 results . push ( ...vvvResults ) ;
710- showPhaseProgress ( "VVV Installation" , vvvResults ) ;
737+ showPhaseProgress ( "VVV Installation" , vvvResults , phaseStart ) ;
711738
712739 // Phase 3: VM State (determines if we can continue)
740+ phaseStart = Date . now ( ) ;
713741 cli . info ( "Checking VM state..." ) ;
714742 verbose ( "Checking VM state..." ) ;
715743 const vmResults = checkVmState ( ctx ) ;
716744 results . push ( ...vmResults ) ;
717- showPhaseProgress ( "VM State" , vmResults ) ;
745+ showPhaseProgress ( "VM State" , vmResults , phaseStart ) ;
718746
719747 // Phase 4: Box Information
748+ phaseStart = Date . now ( ) ;
720749 cli . info ( "Checking box information..." ) ;
721750 verbose ( "Checking box information..." ) ;
722751 const boxResults = checkBoxInfo ( ctx ) ;
723752 results . push ( ...boxResults ) ;
724- showPhaseProgress ( "Box Information" , boxResults ) ;
753+ showPhaseProgress ( "Box Information" , boxResults , phaseStart ) ;
725754
726755 // Check for port conflicts when using Docker and VM is NOT running
727756 // (if VM is running, it's using those ports legitimately)
@@ -754,39 +783,44 @@ async function runAllChecks(vvvPath: string): Promise<CheckResult[]> {
754783 }
755784
756785 // Phase 5: Services (requires VM)
786+ phaseStart = Date . now ( ) ;
757787 cli . info ( "Checking services..." ) ;
758788 verbose ( "Checking services..." ) ;
759789 const serviceResults = checkServices ( ctx ) ;
760790 results . push ( ...serviceResults ) ;
761- showPhaseProgress ( "Services" , serviceResults ) ;
791+ showPhaseProgress ( "Services" , serviceResults , phaseStart ) ;
762792
763793 // Phase 6: Network (requires VM)
794+ phaseStart = Date . now ( ) ;
764795 cli . info ( "Checking network..." ) ;
765796 verbose ( "Checking network..." ) ;
766797 const networkResults = await checkNetwork ( ctx ) ;
767798 results . push ( ...networkResults ) ;
768- showPhaseProgress ( "Network" , networkResults ) ;
799+ showPhaseProgress ( "Network" , networkResults , phaseStart ) ;
769800
770801 // Phase 7: Database (requires VM)
802+ phaseStart = Date . now ( ) ;
771803 cli . info ( "Checking database..." ) ;
772804 verbose ( "Checking database..." ) ;
773805 const dbResults = checkDatabase ( ctx ) ;
774806 results . push ( ...dbResults ) ;
775- showPhaseProgress ( "Database" , dbResults ) ;
807+ showPhaseProgress ( "Database" , dbResults , phaseStart ) ;
776808
777809 // Phase 8: Configuration (can run without VM but more useful with)
810+ phaseStart = Date . now ( ) ;
778811 cli . info ( "Checking configuration..." ) ;
779812 verbose ( "Checking configuration..." ) ;
780813 const configResults = checkConfiguration ( ctx ) ;
781814 results . push ( ...configResults ) ;
782- showPhaseProgress ( "Configuration" , configResults ) ;
815+ showPhaseProgress ( "Configuration" , configResults , phaseStart ) ;
783816
784817 // Phase 9: Log files (requires VM)
818+ phaseStart = Date . now ( ) ;
785819 cli . info ( "Checking log files..." ) ;
786820 verbose ( "Checking log files..." ) ;
787821 const logResults = checkLogFiles ( ctx ) ;
788822 results . push ( ...logResults ) ;
789- showPhaseProgress ( "Log Files" , logResults ) ;
823+ showPhaseProgress ( "Log Files" , logResults , phaseStart ) ;
790824
791825 return results ;
792826}
0 commit comments