para aplicaciones PHP y Laravel
Sin hilo en segundo plano. Sin perder nada.
Un worker de PHP no puede ejecutar un temporizador, y todo lo que aprendió muere con la petición. Ese es el problema que este agente tuvo que resolver antes de poder medir nada: las ventanas se agrupan en memoria compartida y se fusionan — sin pérdidas, o los percentiles serían ficción —, y el envío ocurre después de que la respuesta ya se haya ido, así que un colector con un mal día no le cuesta nada a quien está leyendo.
Qué mide
- Cada petición, y los métodos por debajo
- Laravel y Symfony conectan el span de la petición ellos mismos. Sixty::trace('OrdersQuery#forUser', fn () => …) marca el código entre el controlador y la base de datos — una línea en vez de un decorador, porque PHP no tiene paso de compilación que reescriba funciones ni un gancho que se dispare cuando se define un método.
- Cada consulta, sin sustituir tu conexión
- PDO se instrumenta fijando la clase de sentencia en la conexión que ya existe. Una subclase de PDO tendría que abrir la suya, duplicando en silencio el número de conexiones de cada despliegue — que es justo el tipo de cosa que una herramienta de monitorización no debe hacer nunca.
- Laravel y Doctrine, en cualquier driver
- Las conexiones de Laravel se recogen en ConnectionEstablished, con las filas de rowCount(). Doctrine DBAL recibe un middleware registrado por el bundle, con las filas del propio count del resultado. Ambos te dan la forma de la sentencia y la atribución.
- MongoDB, incluido Doctrine ODM
- A través de la monitorización de comandos del driver. La identidad se construye solo con claves, así que ningún valor tuyo tiene camino hacia ella — y se cuentan las idas y vueltas del cursor, que es la señal que detecta una lectura que llega en trescientas entregas.
- Qué hizo la base de datos para responder
- Un EXPLAIN de plan genérico en Postgres, cacheado por sentencia y emitido fuera del span de quien llama, así que «esta sentencia dejó de usar su índice» llega sin que se enlace nunca un parámetro tuyo.
Qué encuentra
Cada una de estas se compara con la versión anterior, así que el hallazgo nombra el cambio y no el día.
Una consulta que dejó de usar su índice
El hallazgo que nombra una causa en vez de un síntoma. A menudo llega mientras todo sigue siendo rápido, porque la tabla todavía es pequeña — y se convierte en una caída algunas semanas después, cuando ya no lo es.
Un N+1 que no estaba en la versión anterior
Una consulta por petición se convirtió en veinte. Todas rápidas, todas indexadas, y lo único que cambió es el número.
Una consulta que empezó a leer la tabla entera
Las filas por llamada pasaron de 30 a 30.000, normalmente un where que se salió de la consulta y se metió en PHP durante una refactorización.
Una lectura que se convirtió en cientos de esperas de red
MongoDB responde un cursor por lotes. Cuando un resultado supera un lote, una lectura se convierte en decenas o cientos de idas y vueltas secuenciales — documentos idénticos, y no se mueve nada salvo el reloj.
Tu propio código volviéndose más lento
Tiempo en el propio método y no en lo que llamó, separado, para que mires en el archivo correcto al primer intento.
Una consulta que calladamente no devuelve nada
Funcionó, no lanzó nada, no fue lenta, y volvió vacía donde antes volvía con filas.
Qué aparece en tu lista
No es un panel que leer. Una tarjeta por hallazgo, con las cifras, la versión que las cambió y las pruebas que tu agente de código necesita para escribir el arreglo.
Estas son las dos regresiones que apps/demo-laravel incluye a propósito, contra el conjunto de datos que crea su seeder — unos 30.000 pedidos. Puedes ejecutarlo y verlas llegar.
OrdersQuery#forUserLa restricción se salió de la consulta y se metió en PHP durante una refactorización, así que cada llamada carga la tabla entera. La misma respuesta, la misma revisión de código, y unos milisegundos en una base de datos de desarrollo.
desde la versión v2, frente a la v1
OrdersQuery#enrichUna búsqueda por lotes se convirtió en una por pedido. Cada consulta es rápida y está indexada, así que ninguna petición parece mal — el número es lo único que se movió.
desde la versión v2, frente a la v1
Cómo se instala
Laravel descubre el service provider; Symfony necesita que añadas el bundle a config/bundles.php. Ambos conectan después el span de la petición, la instrumentación de consultas y el envío — no hay inicializador que escribir ni nada que llamar.
claude mcp add sixty \
-e SIXTY_API_KEY=sixty_sk_… \
-e SIXTY_ENDPOINT=https://ingest.sixty.sh \
-- npx -y @sixty-sh/mcp
# or by hand:
composer require sixty-sh/sixty
SIXTY_API_KEY=sixty_sk_…
SIXTY_SERVICE=checkout-api
# On Laravel, that is the install — the provider is discovered.
# On Symfony, add the bundle to config/bundles.php.
# Anything else:
\Sixty\Sixty::init();
\Sixty\Instrument\Pdo::instrument($pdo);
# Your own code:
\Sixty\Sixty::trace('OrdersQuery#forUser', fn () => ...);Instala además ext-apcu y las ventanas de cada worker se fusionan en una carga por intervalo. Sin él el agente sigue funcionando y lo dice una vez al arrancar en lugar de fingir lo contrario — sencillamente es bastante más tráfico. El identificador de versión viene de tu plataforma por su cuenta, o de SIXTY_RELEASE.
Qué no hace
- Se niega a activarse bajo corrutinas de Swoole, y dice por qué. Allí muchas peticiones comparten un worker y cambian en cada frontera de E/S, así que el span actual acreditaría las consultas de una petición al controlador de otra. FPM, CLI, RoadRunner y FrankenPHP son un proceso por petición y están completamente soportados.
- Sin ext-apcu cada petición envía su propia ventana. Sigue funcionando y el agente te lo dice una vez al arrancar — pero la fusión sin pérdidas entre workers es lo que hace que los percentiles sean lo que habría reportado un único proceso midiéndolo todo, y sin ella lo pagas en tráfico.
- PDO::query() y PDO::exec() nunca llegan a una clase de sentencia, así que una conexión que construyas tú y sobre la que llames a esas necesita TracedPdo. prepare() + execute() — cada consulta que emiten Laravel y Doctrine — sí está cubierto.
- Si algo más ya es dueño de la clase de sentencia — un profiler, una barra de depuración — el agente lo deja en paz y no mide nada en vez de romperlo. Ese es el intercambio correcto y es silencioso, así que conviene saber que puede pasar.
- La CPU y la espera no se separan, y los planes de consulta son solo de Postgres. MySQL y MongoDB solo pueden explicar una consulta que todavía tenga sus valores dentro, y este agente descarta los valores en cuanto los ve.
Si estás aquí por una de estas
Descubre qué hizo tu último cambio.
¡Pruébalo!¿Construyes otra cosa? Mira servicios Node, aplicaciones Ruby y Rails o servicios Python — esas páginas son distintas, y también lo es lo que podemos ver.