monorepo/native/wordpress/maplepress-plugin/includes/class-speedtest-simple.php

340 lines
9.3 KiB
PHP

<?php
/**
* Simple Speed Test Class
*
* Just runs queries and times them. No complexity.
*
* @package MaplePress_SearchSpeedTest
*/
class MPSS_SpeedTest_Simple {
/**
* Run speed test with specified number of queries.
*
* @param int $query_count Number of queries to run.
* @param string $execution_mode Execution mode: 'serial' or 'parallel'.
* @return array Results with timing data.
*/
public function run( $query_count = 10, $execution_mode = 'serial' ) {
$start_time = microtime( true );
// Debug logging
error_log( 'MPSS_SpeedTest_Simple::run() called with mode: ' . $execution_mode );
// Generate search queries
$queries = $this->generate_queries( $query_count );
// Execute based on mode
if ( $execution_mode === 'parallel' ) {
error_log( 'MPSS: Executing in PARALLEL mode' );
$results = $this->run_parallel( $queries );
} else {
error_log( 'MPSS: Executing in SERIAL mode' );
$results = $this->run_serial( $queries );
}
$end_time = microtime( true );
$total_time = $end_time - $start_time;
// Calculate statistics
$times = array_column( $results, 'duration_ms' );
$avg_ms = array_sum( $times ) / count( $times );
$min_ms = min( $times );
$max_ms = max( $times );
return array(
'results' => $results,
'total_time' => round( $total_time, 2 ),
'total_queries' => count( $results ),
'avg_ms' => round( $avg_ms, 2 ),
'min_ms' => round( $min_ms, 2 ),
'max_ms' => round( $max_ms, 2 ),
'mode' => $execution_mode,
);
}
/**
* Run queries in serial mode (one at a time).
*
* @param array $queries Array of search query strings.
* @return array Results array.
*/
private function run_serial( $queries ) {
$results = array();
foreach ( $queries as $query ) {
$query_start = microtime( true );
// Make HTTP request to search URL to properly trigger MaplePress hooks
$search_url = home_url( '/?s=' . urlencode( $query ) );
$response = wp_remote_get(
$search_url,
array(
'timeout' => 30,
'headers' => array(
'X-MaplePress-Test' => '1',
),
)
);
$query_end = microtime( true );
$duration_ms = ( $query_end - $query_start ) * 1000;
// Extract result count from response - try multiple patterns
$result_count = 0;
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
$body = wp_remote_retrieve_body( $response );
// Try various patterns to find result count
$patterns = array(
'/(\d+)\s+results?/i', // "5 results" or "1 result"
'/found\s+(\d+)/i', // "Found 5"
'/showing\s+(\d+)/i', // "Showing 5"
'/<article/i', // Count article tags
'/<div[^>]*class="[^"]*post[^"]*"/i', // Count post divs
);
foreach ( $patterns as $pattern ) {
if ( $pattern === '/<article/i' || strpos( $pattern, '<div' ) === 0 ) {
// Count HTML elements
$count = preg_match_all( $pattern, $body );
if ( $count > 0 ) {
$result_count = $count;
break;
}
} else {
// Extract number from text
if ( preg_match( $pattern, $body, $matches ) ) {
$result_count = intval( $matches[1] );
break;
}
}
}
}
$results[] = array(
'query' => $query,
'duration_ms' => round( $duration_ms, 2 ),
'result_count' => $result_count,
);
}
return $results;
}
/**
* Run queries in parallel mode (multiple concurrent requests).
*
* @param array $queries Array of search query strings.
* @return array Results array.
*/
private function run_parallel( $queries ) {
error_log( 'MPSS: run_parallel called with ' . count( $queries ) . ' queries' );
$results = array();
// Build search URLs
$search_urls = array();
foreach ( $queries as $query ) {
$search_urls[] = array(
'url' => home_url( '/?s=' . urlencode( $query ) ),
'query' => $query,
);
}
error_log( 'MPSS: Built ' . count( $search_urls ) . ' search URLs' );
// Initialize cURL multi handle
$mh = curl_multi_init();
$curl_handles = array();
$request_info = array();
// Add all requests to multi handle
foreach ( $search_urls as $index => $search_data ) {
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $search_data['url'] );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $ch, CURLOPT_TIMEOUT, 30 );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
'X-MaplePress-Test: 1',
) );
curl_multi_add_handle( $mh, $ch );
$ch_id = (int) $ch;
$curl_handles[ $ch_id ] = $ch;
$request_info[ $ch_id ] = array(
'query' => $search_data['query'],
'index' => $index,
'start_time' => null,
'end_time' => null,
);
}
error_log( 'MPSS: Added ' . count( $curl_handles ) . ' handles, request_info has ' . count( $request_info ) . ' entries' );
// Execute all queries simultaneously and track start/end times
$running = null;
// Record start time for ALL requests (they all start together)
$batch_start = microtime( true );
foreach ( $request_info as $ch_id => $info ) {
$request_info[ $ch_id ]['start_time'] = $batch_start;
}
// Start executing - all requests run concurrently
do {
curl_multi_exec( $mh, $running );
// Check for completed requests and record their end time
while ( $done = curl_multi_info_read( $mh ) ) {
if ( $done['msg'] == CURLMSG_DONE ) {
$ch_id = (int) $done['handle'];
// Record when THIS specific request finished
if ( ! isset( $request_info[ $ch_id ]['end_time'] ) ) {
$request_info[ $ch_id ]['end_time'] = microtime( true );
}
}
}
if ( $running > 0 ) {
curl_multi_select( $mh );
}
} while ( $running > 0 );
$batch_end = microtime( true );
$batch_duration_ms = ( $batch_end - $batch_start ) * 1000;
error_log( 'MPSS: Batch completed in ' . round( $batch_duration_ms, 2 ) . 'ms, processing ' . count( $request_info ) . ' results' );
// Collect results - calculate duration from start/end times
foreach ( $request_info as $ch_id => $info ) {
$ch = $curl_handles[ $ch_id ];
// Calculate duration from start to end time
if ( isset( $info['start_time'] ) && isset( $info['end_time'] ) ) {
$duration_seconds = $info['end_time'] - $info['start_time'];
$duration_ms = $duration_seconds * 1000;
// Debug: log a few samples
static $debug_count = 0;
if ( $debug_count < 3 ) {
error_log( sprintf(
'MPSS: Sample #%d - start=%.6f, end=%.6f, duration_sec=%.6f, duration_ms=%.2f',
$debug_count,
$info['start_time'],
$info['end_time'],
$duration_seconds,
$duration_ms
) );
$debug_count++;
}
} else {
// Fallback - shouldn't happen
$duration_ms = 0;
}
$curl_info = curl_getinfo( $ch );
$response = curl_multi_getcontent( $ch );
$http_code = $curl_info['http_code'];
// Extract result count using multiple patterns
$result_count = 0;
if ( $http_code === 200 && ! empty( $response ) ) {
$patterns = array(
'/(\d+)\s+results?/i',
'/found\s+(\d+)/i',
'/showing\s+(\d+)/i',
'/<article/i',
'/<div[^>]*class="[^"]*post[^"]*"/i',
);
foreach ( $patterns as $pattern ) {
if ( $pattern === '/<article/i' || strpos( $pattern, '<div' ) === 0 ) {
$count = preg_match_all( $pattern, $response );
if ( $count > 0 ) {
$result_count = $count;
break;
}
} else {
if ( preg_match( $pattern, $response, $matches ) ) {
$result_count = intval( $matches[1] );
break;
}
}
}
}
$results[ $info['index'] ] = array(
'query' => $info['query'],
'duration_ms' => round( $duration_ms, 2 ),
'result_count' => $result_count,
);
curl_multi_remove_handle( $mh, $ch );
curl_close( $ch );
}
curl_multi_close( $mh );
// Sort results by original index to maintain query order
ksort( $results );
return array_values( $results );
}
/**
* Generate random search queries from existing content.
*
* @param int $count Number of queries to generate.
* @return array Array of search query strings.
*/
private function generate_queries( $count ) {
error_log( 'MPSS: generate_queries called with count=' . $count );
$posts = get_posts(
array(
'post_type' => array( 'post', 'page' ),
'post_status' => 'publish',
'posts_per_page' => $count * 3,
'orderby' => 'rand',
)
);
error_log( 'MPSS: get_posts returned ' . count( $posts ) . ' posts' );
$queries = array();
foreach ( $posts as $post ) {
if ( count( $queries ) >= $count ) {
break;
}
// Get words from title
$words = array_filter( explode( ' ', $post->post_title ) );
if ( empty( $words ) ) {
continue;
}
// 50% single word, 50% two words
if ( mt_rand( 1, 2 ) === 1 || count( $words ) === 1 ) {
$queries[] = $words[ array_rand( $words ) ];
} else {
$start = mt_rand( 0, count( $words ) - 2 );
$queries[] = $words[ $start ] . ' ' . $words[ $start + 1 ];
}
}
// If we don't have enough, pad with generic searches
while ( count( $queries ) < $count ) {
$queries[] = 'test';
}
$final_queries = array_slice( $queries, 0, $count );
error_log( 'MPSS: generate_queries returning ' . count( $final_queries ) . ' queries' );
return $final_queries;
}
}