Como crear un inspector de rendimiento del theme con PHP en WordPress

Contents

Introducción

Este artículo muestra, paso a paso y con todo lujo de detalles, cómo crear un inspector de rendimiento para el theme (o para un sitio WordPress) usando PHP. El objetivo es disponer de una herramienta ligera que recoja métricas clave (tiempo total de carga, tiempos entre hitos, consultas SQL, memoria usada, archivos incluidos, scripts/styles encolados, etc.) y las muestre en una pequeña interfaz para administradores o durante desarrollo.

Qué mide este inspector y por qué es útil

  • Tiempo total de generación: para detectar páginas lentas.
  • Tiempos por hitos (init, wp, template_redirect, wp_head, render): ayuda a localizar en qué fase se consume más tiempo.
  • Consultas SQL (cantidad, duración y, opcionalmente, listado): identifica consultas pesadas y duplicadas.
  • Memoria usada y pico de memoria: para detectar leaks o consumo excesivo.
  • Archivos incluidos y su peso: para optimizar includes y autoloaders.
  • Scripts y estilos encolados: detectar scripts innecesarios cargados en frontend.

Requisitos y consideraciones iniciales

  1. Acceso al código del theme o a la carpeta de plugins (puedes crear un plugin simple o añadir a functions.php del theme durante desarrollo).
  2. Recomendación: activar visualización solo para usuarios con capacidad de administrador (current_user_can(manage_options)) o cuando WP_DEBUG esté activo y seas usuario logueado. Esto evita exponer datos en producción.
  3. Tener en cuenta el impacto: activar logging de consultas (save_queries) y recolectar stacks puede añadir overhead. Úsalo en desarrollo o para debugging puntual.

Estructura general del inspector

  1. Capturar un timestamp inicial lo antes posible.
  2. Registrar marcas (marks) en acciones clave de WP: init, wp, template_redirect, wp_head, wp_footer, shutdown.
  3. Habilitar (opcional) wpdb->save_queries para poder listar consultas y su duración.
  4. Al renderizar wp_footer (o justo antes del final), calcular métricas, formatearlas y mostrar un panel solo para administradores.
  5. Opcional: listar las top N consultas, archivos incluidos más pesados y scripts/styles encolados.

Código: plugin mínimo (archivo único)

Guarda este código en un archivo llamado, por ejemplo, theme-performance-inspector.php dentro de wp-content/plugins/ y actívalo desde el panel de plugins. También puedes pegarlo temporalmente en functions.php durante pruebas.

lt?php
/
Plugin Name: Theme Performance Inspector
Description: Inspector sencillo de rendimiento para themes. Muestra tiempos, consultas, memoria, archivos incluidos y recursos encolados.
Version: 1.0
Author: Inspector Automático
/

if ( ! defined( ABSPATH ) ) {
    exit
}

class Theme_Performance_Inspector {

    private start_time
    private marks = array()
    private queries_enabled = false

    public function __construct() {
        this->start_time = microtime(true)
        this->marks[boot] = this->start_time

        add_action(init, array(this, mark_init), 0)
        add_action(wp, array(this, mark_wp), 0)
        add_action(template_redirect, array(this, mark_template), 0)
        add_action(wp_head, array(this, mark_wp_head), 0)
        add_action(wp_footer, array(this, render_panel), 9999)
        add_action(shutdown, array(this, mark_shutdown), 0)
    }

    public function mark_init() {
        this->marks[init] = microtime(true)
        global wpdb
        // Activar guardado de queries si WP_DEBUG o SAVEQUERIES está activo,
        // o si quieres forzarlo solo para admins en desarrollo.
        if ( ( defined(SAVEQUERIES)  SAVEQUERIES )  ( defined(WP_DEBUG)  WP_DEBUG ) ) {
            wpdb->save_queries = true
            this->queries_enabled = true
        }
    }

    public function mark_wp() {
        this->marks[wp] = microtime(true)
    }

    public function mark_template() {
        this->marks[template_redirect] = microtime(true)
    }

    public function mark_wp_head() {
        this->marks[wp_head] = microtime(true)
    }

    public function mark_shutdown() {
        this->marks[shutdown] = microtime(true)
    }

    private function is_allowed_to_view() {
        // Mostrar solo a administradores o si WP_DEBUG y usuario logueado.
        if ( is_admin() ) {
            return false
        }
        if ( current_user_can(manage_options) ) {
            return true
        }
        if ( defined(WP_DEBUG)  WP_DEBUG  is_user_logged_in() ) {
            return true
        }
        return false
    }

    public function render_panel() {
        if ( ! this->is_allowed_to_view() ) {
            return
        }

        global wpdb, wp_scripts, wp_styles

        end_time = microtime(true)
        total_time = round( ( end_time - this->start_time )  1000, 2 ) // ms

        this->marks[render] = end_time

        mem = function_exists(memory_get_usage) ? size_format(memory_get_usage(true)) : n/a
        mem_peak = function_exists(memory_get_peak_usage) ? size_format(memory_get_peak_usage(true)) : n/a

        included_files = get_included_files()
        included_count = count(included_files)

        queries = array()
        num_queries = 0
        if ( isset(wpdb->num_queries) ) {
            num_queries = wpdb->num_queries
        } elseif ( isset(wpdb->queries) ) {
            num_queries = count(wpdb->queries)
        }
        if ( this->queries_enabled  ! empty(wpdb->queries) ) {
            queries = wpdb->queries
        }

        // Top archivos por tamaño (limit 10)
        files_info = array()
        foreach ( included_files as f ) {
            if ( is_readable(f) ) {
                files_info[] = array(
                    file => f,
                    size => filesize(f),
                )
            }
        }
        usort(files_info, function(a,b){ return b[size] - a[size] })
        files_info = array_slice(files_info, 0, 10)

        // Scripts y estilos encolados
        scripts = array()
        if ( isset(wp_scripts)  isset(wp_scripts->queue) ) {
            scripts = wp_scripts->queue
        }
        styles = array()
        if ( isset(wp_styles)  isset(wp_styles->queue) ) {
            styles = wp_styles->queue
        }

        // HTML del panel (simple, minimalista)
        echo nn
        echo 
echo
echo
echo
Inspector de rendimiento theme
echo
.total_time. ms
echo
echo
echo
Memoria: {mem} (pico: {mem_peak})
echo
Archivos incluidos: {included_count} (top mostrados abajo)
echo
Consultas SQL: {num_queries} .(this->queries_enabled ? (guardadas) : (no guardadas)).
echo
Scripts encolados: .count(scripts).
echo
Estilos encolados: .count(styles).
echo
// Marcas y duraciones echo
echo Hitos echo
    prev = this->start_time foreach ( this->marks as k => t ) { ms = round( ( t - this->start_time ) 1000, 2 ) delta = round( ( t - prev ) 1000, 2 ) echo
  • . esc_html(k) . : {ms} ms (Δ {delta} ms)
  • prev = t } echo
echo
// Top archivos if ( ! empty(files_info) ) { echo
echo Archivos (por tamaño) echo
    foreach ( files_info as f ) { echo
  • .basename(f[file]). — .size_format(f[size]).
  • } echo
echo
} // Consultas (las 20 más lentas si hay) if ( ! empty(queries) is_array(queries) ) { // wpdb->queries often holds arrays: [ query, time, caller ] // Normalizar y ordenar por tiempo normalized = array() foreach ( queries as q ) { if ( is_array(q) ) { normalized[] = array( query => q[0], time => isset(q[1]) ? floatval(q[1]) : 0, ) } else { normalized[] = array(query => strval(q), time => 0) } } usort(normalized, function(a,b){ return b[time] <=> a[time] }) normalized = array_slice(normalized, 0, 20) echo
echo Consultas (top 20 por tiempo) echo
    foreach ( normalized as q ) { tms = round(q[time] 1000, 2) echo
  • .tms. ms.esc_html( wp_trim_words(q[query], 30, ...) ).
  • } echo
echo
} // Enlaces para expandir/ocultar (solo HTML minimal) echo
echo Cerrar echo
echo
// panel inner echo
n echo n } } new Theme_Performance_Inspector()

Explicación detallada del código

  1. Inicio temprano: en el constructor se captura microtime(true) para conocer la marca boot.
  2. Marcas en acciones clave: se añaden hooks en init, wp, template_redirect, wp_head y shutdown. Cada marca guarda el timestamp para comparar duraciones entre fases.
  3. Habilitar guardado de queries: si WP_DEBUG o SAVEQUERIES está activo, el inspector activa wpdb->save_queries para poder listar las consultas y sus tiempos. Esto incrementa el uso de memoria y algo de overhead, por eso se recomienda limitarlo a desarrollo.
  4. Renderizado del panel: en wp_footer (prioridad alta) se construye un bloque HTML con los datos. Solo lo mostramos si current_user_can(manage_options) o en modo debug con usuario logueado.
  5. Listado de archivos: get_included_files() y filesize() sirven para detectar archivos grandes cargados durante la petición.
  6. Consultas: si wpdb->queries está poblado, se normalizan y ordenan por tiempo para mostrar las más costosas.
  7. Seguridad y escape: en el código del ejemplo se usan funciones de escape básicas donde es sencillo (esc_html y wp_trim_words). En un entorno real conviene reforzarlo según el contenido mostrado.

Ejemplo de CSS y JS externo (opcional)

Si prefieres separar estilos y controlar la interfaz mejor, puedes encolar un CSS/JS y añadir toggles. Ejemplo CSS mínimo:

/ tpi.css: estilos mínimos para el panel /
#tpi-panel { font-family: Inter, Roboto, Arial, sans-serif }
#tpi-panel .tpi-header { display:flex justify-content:space-between align-items:center }
#tpi-panel .tpi-body { margin-top:8px font-size:13px color:#ddd }

Ejemplo de JS mínimo para toggles (tpi.js):

// tpi.js
(function(){
  document.addEventListener(click, function(e){
    if (e.target.matches(.tpi-toggle)) {
      e.preventDefault()
      var box = document.getElementById(tpi-panel)
      if (box) box.classList.toggle(tpi-collapsed)
    }
  })
})()

Cómo instalar y activar

  1. Crear un archivo PHP con el contenido del ejemplo y subirlo a wp-content/plugins/ (por ejemplo: theme-performance-inspector.php).
  2. Activar el plugin desde el escritorio de WordPress.
  3. Visitar el frontend como administrador para ver el panel en la esquina inferior derecha.
  4. Si prefieres probar sin plugin, pega el código en functions.php de tu theme (solo en un entorno de desarrollo).

Buenas prácticas y recomendaciones

  • Solo para desarrollo o admins: nunca dejar wpdb->save_queries activado en producción para sitios con alta carga.
  • Atención al overhead: recolectar el stack trace por cada query o mostrar listas muy largas puede afectar rendimiento. Limita a top N resultados.
  • Usa transients o logs externos si quieres almacenar datos históricos o fusionar resultados de múltiples páginas.
  • Integración con Xdebug / Blackfire: para análisis profundo, usar herramientas de profiling. Este inspector es útil para detección rápida y localización general del problema.
  • Evitar exposición de datos sensibles: el listado de consultas puede incluir datos no lo muestres a usuarios no confiables.

Posibles extensiones y mejoras

  1. Agregar trazas de hooks para identificar callbacks lentos (registrar microtime en add_action para hooks más específicos).
  2. Medir tiempos de llamadas HTTP externas (hook para wp_remote_get y medir duración).
  3. Integrar con el bar de administración de WP (wp-admin bar) para una visualización más integrada.
  4. Permitir exportar el informe a JSON para análisis posterior.
  5. Agregar un modo mu-plugin para captar timestamps aún más temprano (mu-plugins se cargan antes que plugins normales).

Tabla resumen de métricas recogidas

Métrica Descripción Cómo ayuda
Tiempo total Tiempo desde el arranque del plugin hasta el render final (ms) Detectar páginas lentas
Tiempos por hitos Marcas en init, wp, template_redirect, wp_head, shutdown Localizar fase problemática
Consultas SQL Número y lista (si save_queries) Detectar consultas costosas y duplicadas
Memoria Uso actual y pico Detectar consumo excesivo
Archivos incluidos Listado y tamaños Detectar includes innecesarios o pesados
Recursos encolados Scripts y estilos activos Detectar assets cargados innecesariamente

Notas finales

El inspector que aquí se describe es intencionalmente simple, portátil y fácil de extender. Su propósito es dar una primera aproximación y permitir localizar rápidamente cuellos de botella en la carga del theme o en la ejecución de una petición. Para análisis de producción a gran escala es preferible usar perfiles con herramientas especializadas, pero esta herramienta es perfecta para debugging local y optimizaciones puntuales.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *