'locked' ); } try { $report = self::warm( ZeroConfig_Probe::candidates() ); } finally { ZeroConfig_Options::unlock( 'warm' ); } ZeroConfig_Probe::save_health( array( 'warm' => $report ) ); // The doctor runs on the same requests' heels when its last verdict is over an hour old. $doc = (int) ( ZeroConfig_Probe::health()['doctor']['at'] ?? 0 ); if ( 'loopback_blocked' !== $report['stop'] && time() - $doc > HOUR_IN_SECONDS ) { ZeroConfig_Doctor::run(); } return $report; } /** Request each address on desktop and mobile, measure, stop early when the server struggles. */ public static function warm( array $urls ) { $gen = ZeroConfig_Page_Cache::generation(); $t0 = microtime( true ); $items = array(); $times = array(); $slow = 0; $stop = ''; $n = 0; foreach ( $urls as $url ) { foreach ( array( 'd', 'm' ) as $device ) { if ( ZeroConfig_Page_Cache::generation() !== $gen ) { $stop = 'purged'; // a new purge would delete what we write; its own warm-up follows break 2; } if ( microtime( true ) - $t0 > self::BUDGET_S ) { $stop = 'time'; break 2; } if ( self::overloaded() ) { $stop = 'load'; break 2; } if ( $n++ ) { usleep( self::GAP_MS * 1000 ); // between every request, the two devices of one page too } $r = ZeroConfig_Probe::fetch( $url, $device, true ); $items[] = array( 'url' => $url, 'device' => $device, 'ms' => $r['ms'], 'layer' => $r['layer'], 'stored' => is_file( ZeroConfig_Probe::file_for( $url, $device ) ), ); if ( 'error' === $r['layer'] && ! ZeroConfig_Probe::timed_out( $r ) ) { $stop = 'loopback_blocked'; // never fall back to the public address (see class comment) break 2; } // The first answer only calibrates. After it, a page is slow when it takes longer than both // 5 s and three times the usual; one slow page is data, two in a row mean stop. A timeout is // a slow answer, not a blocked one, and it never enters the median: a single 8 s sample would // otherwise lift the limit to 24 s and switch the rule off exactly when the server struggles. $timeout = ZeroConfig_Probe::timed_out( $r ); if ( $n > 1 ) { $limit = $times ? max( 5000, 3 * self::median( $times ) ) : 5000; $slow = $timeout || $r['ms'] > $limit ? $slow + 1 : 0; if ( $slow >= 2 ) { $stop = 'slow'; break 2; } } if ( ! $timeout ) { $times[] = $r['ms']; } } } // One more request for the first address: how fast a cached page comes back. $warm = array( 'ms' => 0, 'layer' => '' ); if ( '' === $stop && $urls ) { usleep( self::GAP_MS * 1000 ); $warm = ZeroConfig_Probe::fetch( $urls[0], 'd', true ); } $stored = count( array_filter( wp_list_pluck( $items, 'stored' ) ) ); return array( 'at' => time(), 'urls' => count( $urls ), 'requests' => count( $items ), 'stored' => $stored, 'seconds' => round( microtime( true ) - $t0, 1 ), 'cold_ms' => $times ? (int) self::median( $times ) : 0, 'warm_ms' => (int) $warm['ms'], 'warm_layer' => (string) $warm['layer'], 'stop' => $stop, 'items' => $items, ); } /** Only where the host lets us see the load: above 80 % of the cores, stop. */ private static function overloaded() { if ( ! function_exists( 'sys_getloadavg' ) ) { return false; } $load = @sys_getloadavg(); // phpcs:ignore WordPress.PHP.NoSilencedErrors if ( ! is_array( $load ) || ! isset( $load[0] ) ) { return false; } $cores = 1; // The core count, on Linux hosts that let PHP read it (open_basedir usually does not). if ( ! ini_get( 'open_basedir' ) && is_readable( '/proc/cpuinfo' ) ) { $cores = max( 1, substr_count( (string) file_get_contents( '/proc/cpuinfo' ), 'processor' ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- a local system file, read only } return $load[0] > $cores * 0.8; } private static function median( array $v ) { sort( $v ); $c = count( $v ); return $c ? ( $c % 2 ? $v[ intdiv( $c, 2 ) ] : ( $v[ $c / 2 - 1 ] + $v[ $c / 2 ] ) / 2 ) : 0; } }