Files
roi-theme/wp-content/plugins/wp-debug/inc/query-analyzer.php

267 lines
7.8 KiB
PHP

<?php
/**
* Query Analyzer Module
*
* Analiza queries SQL, detecta N+1 queries y queries lentas
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Query_Analyzer class
*/
class WP_Debug_Query_Analyzer {
/**
* Queries executed
*
* @var array
*/
private static $queries_executed = array();
/**
* Query patterns
*
* @var array
*/
private static $query_patterns = array();
/**
* Initialize query analyzer
*/
public static function init() {
// Enable query saving
if (!defined('SAVEQUERIES')) {
define('SAVEQUERIES', true);
}
// Track queries on shutdown
add_action('shutdown', array(__CLASS__, 'analyze_queries'), 999);
}
/**
* Analyze queries
*/
public static function analyze_queries() {
global $wpdb;
if (empty($wpdb->queries)) {
return;
}
// Process each query
foreach ($wpdb->queries as $query) {
list($sql, $elapsed, $caller) = $query;
$query_info = array(
'sql' => $sql,
'elapsed' => $elapsed,
'caller' => $caller,
'timestamp' => microtime(true),
);
self::$queries_executed[] = $query_info;
// Group by pattern
$normalized = self::normalize_query($sql);
if (!isset(self::$query_patterns[$normalized])) {
self::$query_patterns[$normalized] = array(
'count' => 0,
'total_time' => 0,
'queries' => array(),
);
}
self::$query_patterns[$normalized]['count']++;
self::$query_patterns[$normalized]['total_time'] += floatval($elapsed);
self::$query_patterns[$normalized]['queries'][] = $query_info;
}
// Log statistics
$total_queries = count($wpdb->queries);
$total_time = array_sum(array_column($wpdb->queries, 1));
$slow_queries = self::get_slow_queries();
$n_plus_one = self::detect_n_plus_one();
if (class_exists('WP_Debug_Logger')) {
WP_Debug_Logger::info('Query Analysis Complete', array(
'total_queries' => $total_queries,
'total_time' => round($total_time, 4) . 's',
'slow_queries' => count($slow_queries),
'n_plus_one_patterns' => count($n_plus_one),
));
// Log slow queries
foreach ($slow_queries as $query) {
WP_Debug_Logger::warning('Slow Query Detected', array(
'elapsed' => round($query['elapsed'], 4) . 's',
'sql' => substr($query['sql'], 0, 200),
'caller' => $query['caller'],
));
}
// Log N+1 patterns
foreach ($n_plus_one as $pattern) {
WP_Debug_Logger::warning('N+1 Query Pattern Detected', array(
'count' => $pattern['count'],
'total_time' => round($pattern['total_time'], 4) . 's',
'example' => substr($pattern['example'], 0, 200),
));
}
}
// Store for frontend panel
set_transient('wp_debug_queries_data', array(
'total_queries' => $total_queries,
'total_time' => $total_time,
'queries' => self::$queries_executed,
'slow_queries' => $slow_queries,
'n_plus_one' => $n_plus_one,
'patterns' => self::$query_patterns,
), 3600);
}
/**
* Normalize query for pattern detection
*
* @param string $query SQL query
* @return string Normalized query
*/
private static function normalize_query($query) {
// Remove numeric literals
$query = preg_replace('/\b\d+\b/', '?', $query);
// Remove string literals
$query = preg_replace("/'[^']*'/", '?', $query);
// Remove extra whitespace
$query = preg_replace('/\s+/', ' ', $query);
return trim($query);
}
/**
* Detect N+1 query patterns
*
* @return array N+1 patterns
*/
public static function detect_n_plus_one() {
$patterns = array();
// Find patterns executed 5+ times
foreach (self::$query_patterns as $normalized => $pattern) {
if ($pattern['count'] >= 5) {
$patterns[] = array(
'count' => $pattern['count'],
'total_time' => $pattern['total_time'],
'avg_time' => $pattern['total_time'] / $pattern['count'],
'example' => $pattern['queries'][0]['sql'],
'normalized' => $normalized,
);
}
}
// Sort by count descending
usort($patterns, function($a, $b) {
return $b['count'] - $a['count'];
});
return $patterns;
}
/**
* Get slow queries
*
* @param float $threshold Threshold in seconds
* @return array Slow queries
*/
public static function get_slow_queries($threshold = 0.05) {
return array_filter(self::$queries_executed, function($query) use ($threshold) {
return floatval($query['elapsed']) > $threshold;
});
}
/**
* Get queries by caller
*
* @param string $caller Caller filter
* @return array Queries
*/
public static function get_queries_by_caller($caller) {
return array_filter(self::$queries_executed, function($query) use ($caller) {
return strpos($query['caller'], $caller) !== false;
});
}
/**
* Get query statistics
*
* @return array Statistics
*/
public static function get_statistics() {
global $wpdb;
if (empty($wpdb->queries)) {
return array(
'error' => 'SAVEQUERIES not enabled or no queries executed',
);
}
$total_queries = count($wpdb->queries);
$total_time = array_sum(array_column($wpdb->queries, 1));
$avg_time = $total_time / $total_queries;
return array(
'total_queries' => $total_queries,
'total_time' => $total_time,
'avg_time' => $avg_time,
'slow_queries' => count(self::get_slow_queries()),
'n_plus_one_patterns' => count(self::detect_n_plus_one()),
'unique_patterns' => count(self::$query_patterns),
);
}
/**
* Get query report
*
* @return array Query report
*/
public static function get_query_report() {
$stats = self::get_statistics();
$slow_queries = self::get_slow_queries();
$n_plus_one = self::detect_n_plus_one();
$suggestions = array();
// Suggestions based on analysis
if (isset($stats['total_queries']) && $stats['total_queries'] > 100) {
$suggestions[] = 'High number of queries detected (' . $stats['total_queries'] . '). Consider using object caching.';
}
if (isset($stats['total_time']) && $stats['total_time'] > 1) {
$suggestions[] = 'Total query time exceeds 1 second. Optimize slow queries.';
}
if (!empty($n_plus_one)) {
$suggestions[] = 'N+1 query patterns detected. Consider using WP_Query with proper parameters or caching.';
}
if (!empty($slow_queries)) {
$suggestions[] = count($slow_queries) . ' slow queries detected. Add indexes or optimize WHERE clauses.';
}
return array(
'statistics' => $stats,
'slow_queries' => array_slice($slow_queries, 0, 10),
'n_plus_one_patterns' => array_slice($n_plus_one, 0, 5),
'suggestions' => $suggestions,
);
}
}