267 lines
7.8 KiB
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,
|
|
);
|
|
}
|
|
}
|