Gestionando el rendimiento
Optimizar antes de tiempo es la raíz de todos los males.
Tal vez ya hayas leído esta cita antes. Pero me gusta citarla en su totalidad:
Deberíamos olvidar las pequeñas optimizaciones el 97 % de las veces; optimizar antes de tiempo es el origen de todos los males. Sin embargo, no deberíamos dejar pasar la oportunidad de optimizar ese crucial 3 %.
-- Donald Knuth
Incluso pequeñas mejoras en el rendimiento pueden marcar la diferencia, especialmente para sitios de comercio electrónico. Ahora que la aplicación del libro de visitas está lista para el gran público, veamos cómo podemos comprobar su rendimiento.
La mejor manera de encontrar optimizaciones de rendimiento es usar un analizador de rendimiento o perfilador (profiler). La opción más popular hoy en día es Blackfire (aviso a navegantes: también soy el fundador del proyecto Blackfire).
Introducción a Blackfire
Blackfire se compone de varias partes:
- Un cliente que activa perfiles (la herramienta CLI de Blackfire o una extensión de navegador para Google Chrome o Firefox);
- Un agente que prepara y agrega datos antes de enviarlos a blackfire.io para su visualización;
- Una extensión PHP (que actúa a modo de sonda) y que analiza el código PHP.
Para trabajar con Blackfire, primero tienes que registrarte .
Instala Blackfire en tu equipo local, ejecutando el siguiente script de instalación rápida:
1
$ curl https://installer.blackfire.io/installer.sh | bash
Este instalador descarga e instala la herramienta de línea de comandos de Blackfire.
Cuando termine, instala la sonda PHP en todas las versiones disponibles de PHP:
1
$ sudo blackfire php:install
Y habilita la sonda PHP para nuestro proyecto:
1 2 3 4 5 6 7 8 9 10
--- i/php.ini
+++ w/php.ini
@@ -7,3 +7,7 @@ session.use_strict_mode=On
realpath_cache_ttl=3600
zend.detect_unicode=Off
xdebug.file_link_format=vscode://file/%f:%l
+
+[blackfire]
+# use php_blackfire.dll on Windows
+extension=blackfire.so
Reinicia el servidor web para que PHP pueda cargar Blackfire:
1 2
$ symfony server:stop
$ symfony server:start -d
La herramienta de línea de comandos de Blackfire necesita estar configurada con tus credenciales personales de cliente (para almacenar los perfiles de tus proyectos en tu cuenta personal). Encuéntralas en la parte superior de la página Settings/Credentials (configuración/credenciales) y ejecuta el siguiente comando reemplazando los marcadores de posición:
1
$ blackfire client:config --client-id=xxx --client-token=xxx
Configuración del agente Blackfire en Docker
El servicio del agente Blackfire ya ha sido configurado en el stack (pila) de servicios de Docker Compose:
1 2 3 4 5 6 7 8 9
###> blackfireio/blackfire-symfony-meta ###
blackfire:
image: blackfire/blackfire:2
# uncomment to store Blackfire credentials in a local .env.local file
#env_file: .env.local
environment:
BLACKFIRE_LOG_LEVEL: 4
ports: [8307]
###< blackfireio/blackfire-symfony-meta ###
Para comunicarse con el servidor, necesitas obtener tus credenciales personales del servidor (estas credenciales identifican dónde deseas almacenar los perfiles; puedes crear una por proyecto); se pueden encontrar en la parte inferior de la página Settings/Credentials. Almacénalas como secretos:
1 2 3 4
$ symfony console secrets:set BLACKFIRE_SERVER_ID
# xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ symfony console secrets:set BLACKFIRE_SERVER_TOKEN
# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Ahora puedes lanzar el nuevo contenedor:
1 2
$ docker compose stop
$ docker compose up -d --remove-orphans
Arreglando una instalación de Blackfire que no funciona
Si obtienes un error durante el análisis de rendimiento, aumenta el nivel de registro de Blackfire para obtener más información:
1 2 3 4 5 6 7
--- i/php.ini
+++ w/php.ini
@@ -10,3 +10,4 @@ zend.detect_unicode=Off
[blackfire]
# use php_blackfire.dll on Windows
extension=blackfire.so
+blackfire.log_level=4
Reinicia el servidor web:
1 2
$ symfony server:stop
$ symfony server:start -d
Y revisa los logs en vivo (en tiempo real):
1
$ symfony server:log
Analiza la aplicación de nuevo y comprueba la salida del log.
Configurando Blackfire en producción
Blackfire está incluido por defecto en todos los proyectos de Upsun.
Configura las credenciales del servidor como secretos de producción:
1 2 3 4
$ symfony console secrets:set BLACKFIRE_SERVER_ID --env=prod
# xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ symfony console secrets:set BLACKFIRE_SERVER_TOKEN --env=prod
# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
La sonda PHP ya está habilitada como cualquier otra extensión PHP necesaria:
1 2 3 4 5 6 7 8 9 10
runtime:
extensions:
- apcu
- blackfire
- ctype
- iconv
- mbstring
- pdo_pgsql
- sodium
- xsl
Configurando Varnish para Blackfire
Antes de que puedas desplegar para empezar a realizar las tareas de análisis, necesitas una forma de evitar la caché HTTP de Varnish. Si no, Blackfire nunca llegará a la aplicación PHP. Vas a autorizar solo las peticiones de profiling que provengan de tu máquina local.
Encuentra tu dirección IP actual:
1
$ curl https://ifconfig.me/
y úsala para configurar Varnish:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
--- i/.upsun/config.vcl
+++ w/.upsun/config.vcl
@@ -1,3 +1,11 @@
+acl profile {
+ # Authorize the local IP address (replace with the IP found above)
+ "192.168.0.1";
+ # Authorize Blackfire servers
+ "46.51.168.2";
+ "54.75.240.245";
+}
+
sub vcl_recv {
set req.backend_hint = application.backend();
set req.http.Surrogate-Capability = "abc=ESI/1.0";
@@ -8,6 +16,16 @@ sub vcl_recv {
}
return (purge);
}
+
+ # Don't profile ESI requests
+ if (req.esi_level > 0) {
+ unset req.http.X-Blackfire-Query;
+ }
+
+ # Bypass Varnish when the profile request comes from a known IP
+ if (req.http.X-Blackfire-Query && client.ip ~ profile) {
+ return (pass);
+ }
}
sub vcl_backend_response {
Ahora puedes realizar el despliegue.
Analizando el rendimiento de páginas web
Puedes hacer análisis de páginas web tradicionales desde Firefox o Google Chrome a través de sus extensiones dedicadas .
No olvides desactivar la caché HTTP en config/packages/framework.yaml cuando realices el análisis en tu máquina local: si no lo haces, analizarás la capa de caché HTTP de Symfony en lugar de tu propio código.
Para tener una mejor idea del rendimiento de tu aplicación en producción, deberías también hacer un análisis del entorno de "producción". Por defecto, tu entorno local utiliza el entorno de "desarrollo", que añade una sobrecarga significativa (principalmente para recopilar datos para la barra de herramientas de depuración web y el analizador de rendimiento de Symfony).
Note
Como vamos a analizar el entorno de "producción", no hay nada que cambiar en la configuración, ya que habilitamos la capa de caché HTTP de Symfony solo para el entorno de "desarrollo" en un capítulo anterior.
El cambio de tu máquina local al entorno de producción se puede hacer cambiando la variable de entorno APP_ENV en el archivo .env.local:
1
APP_ENV=prod
O puedes usar el comando server:prod:
1
$ symfony server:prod
No olvides volver a cambiar al entorno de desarrollo cuando finalice tu sesión de profiling (análisis de rendimiento):
1
$ symfony server:prod --off
Analizando el rendimiento de los recursos de la API
El análisis de rendimiento de la API se realiza mejor en la línea de comandos usando la herramienta Blackfire que se ha instalado previamente:
1
$ blackfire curl `symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL`api
El comando blackfire curl acepta exactamente los mismos argumentos y opciones que cURL .
Comparación del rendimiento
En el paso acerca de la "Caché" añadimos una capa de caché para mejorar el rendimiento de nuestro código, pero no hemos comprobado ni medido el impacto en el rendimiento que ha tenido el cambio. Como las personas somos poco efectivas adivinando qué será rápido y qué será lento, podrías terminar en una situación en la que añadir un poco de optimización hiciera que tu aplicación fuera, realmente, más lenta.
Deberías medir siempre el impacto de cualquier optimización que hagas con un analizador de rendimiento. Blackfire lo hace visualmente más fácil gracias a su función de comparación .
Escribiendo pruebas funcionales de caja negra
Hemos visto cómo escribir pruebas funcionales con Symfony. Blackfire se puede utilizar para escribir escenarios de navegación que se pueden ejecutar bajo demanda a través del Blackfire player . Escribamos un escenario que envíe un nuevo comentario y lo valide en desarrollo a través del enlace de correo electrónico y a través del admin en producción.
Crea un archivo de nombre .blackfire.yaml con el siguiente contenido:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
scenarios: |
#!blackfire-player
group login
visit url('/login')
submit button("Sign in")
param username "admin"
param password "admin"
expect status_code() == 302
scenario
name "Submit a comment on the Amsterdam conference page"
include login
visit url('/fr/conference/amsterdam-2019')
expect status_code() == 200
submit button("Submit")
param comment[author] 'Fabien'
param comment[email] 'me@example.com'
param comment[text] 'Such a good conference!'
param comment[photo] file(fake('simple_image', '/tmp', 400, 300, 'png', true, true), 'placeholder-image.jpg')
expect status_code() == 302
follow
expect status_code() == 200
expect not(body() matches "/Such a good conference/")
# Wait for the workflow to validate the submissions
wait 5000
when env != "prod"
visit url(webmail_url ~ '/messages')
expect status_code() == 200
set message_ids json("[*].id")
with message_id in message_ids
visit url(webmail_url ~ '/messages/' ~ message_id ~ '.html')
expect status_code() == 200
set accept_url css("table a").first().attr("href")
include login
visit url(accept_url)
# we don't check the status code as we can deal
# with "old" messages which do not exist anymore
# in the DB (would be a 404 then)
when env == "prod"
visit url('/admin')
expect status_code() == 302
follow
click link("Comments")
expect status_code() == 200
set comment_ids css('table.table tbody tr').extract('data-id')
with id in comment_ids
visit url('/admin/comment/review/' ~ id)
# we don't check the status code as we scan all comments,
# including the ones already reviewed
visit url('/fr/')
wait 5000
visit url('/fr/conference/amsterdam-2019')
expect body() matches "/Such a good conference/"
Descarga el reproductor Blackfire para poder ejecutar el escenario localmente:
1 2
$ curl -OLsS https://get.blackfire.io/blackfire-player.phar
$ chmod +x blackfire-player.phar
Ejecuta este escenario en desarrollo:
1
$ ./blackfire-player.phar run --endpoint=`symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL` .blackfire.yaml --variable "webmail_url=`symfony var:export MAILER_WEB_URL 2>/dev/null`" --variable="env=dev" -vv
O en producción:
1
$ ./blackfire-player.phar run --endpoint=`symfony cloud:env:url --pipe --primary` .blackfire.yaml --variable "webmail_url=NONE" --variable="env=prod" -vv
Los escenarios de Blackfire también pueden desencadenar perfiles para cada solicitud y ejecutar pruebas de rendimiento añadiendo la opción --blackfire.
Automatizando los análisis de rendimiento
La gestión del rendimiento no sólo consiste en mejorar el rendimiento del código existente, sino también en comprobar que no se introducen regresiones de rendimiento.
El escenario escrito en la sección anterior puede ejecutarse automáticamente en un workflow de integración continua (CI), o en producción, de forma regular.
En Upsun, también se permite ejecutar los escenarios cada vez que se crea una nueva rama o se despliega en producción para analizar automáticamente el rendimiento del nuevo código.