para servicios Python
¿Fue tu código, o fue esperar?
Cualquier otro agente puede decirte que una función se volvió más lenta. Este te dice cuál de las dos mitades — el código que estaba computando, o el tiempo que pasó esperando un lock, un hueco del pool o una extensión en C que retenía el GIL. Esas necesitan arreglos opuestos, y todos los demás números por llamada siguen siendo correctos mientras ocurre la segunda, que es por lo que no la detecta nada más.
Qué mide
- CPU por llamada
- time.thread_time() es por hilo y cuesta unos 100 ns leerlo, y un span síncrono es dueño de su hilo durante toda su duración — así que este es un número exacto y no una parte de un reloj de todo el proceso repartida entre lo que estuviera corriendo.
- Espera por llamada
- El resto del tiempo propio de una función: un lock retenido durante más trabajo del que solía, un pool de conexiones sin hueco libre, una extensión en C que tomó el GIL y no lo soltó. Es invisible para cualquier otra medición aquí porque el código no es más lento — está haciendo cola.
- Cada consulta de psycopg
- Versiones 2 y 3, parcheado en el cursor y no en la conexión, así que SQLAlchemy encima de psycopg también se mide. Filas devueltas, columnas, tamaño de la respuesta y la sentencia normalizada.
- Qué función emitió qué consulta
- Una consulta se acredita a la función que tiene encima, así que un N+1 aterriza en la función que tiene el bucle en vez de repartirse por una consulta que siempre pareció correcta por sí sola.
- Tus rutas HTTP, tal y como están escritas
- Flask por la url_rule que casó, Django por la ruta tal y como aparece en urls.py, y cualquier cosa ASGI o WSGI por lo que envuelvas. Así la lista dice GET /users/<int:user_id>/orders en vez de una operación por cada id de usuario.
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.
Tu propio código volviéndose lento, partido en dos
El hallazgo que dice a qué arreglo echar mano. Subió el tiempo computando y no el de espera: mira dentro de la función. Subió el tiempo de espera y la CPU no se movió: mira el pool, el lock, o con quién comparte hilo ahora el proceso.
Un N+1 que no estaba en la versión anterior
Una función que hacía una consulta por petición ahora hace veinte. Cada una es rápida y está correctamente indexada, así que ningún tiempo se mueve lo suficiente como para notarlo — solo el número.
Una consulta que empezó a leer la tabla entera
Las filas por llamada pasaron de 30 a 30.000. En una base de datos caliente con datos de tamaño desarrollo esto apenas mueve el reloj, y tumba producción cuando hay un año de filas detrás.
Algo que ahora se llama dentro de un bucle
Sin cambios por llamada — misma velocidad, mismos datos — y llamado cientos de veces donde antes se llamaba una.
Una consulta que calladamente no devuelve nada
Funcionó, no lanzó nada, no fue lenta, y volvió vacía donde antes volvía con filas. La mayoría de las formas en que se rompe un servicio así hacen los números más pequeños y no más grandes.
Errores, agrupados por de dónde vinieron
Por ubicación en el código y no por mensaje, así que un fallo es una entrada por muchas cadenas distintas en las que se formatee.
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-python incluye a propósito, contra el conjunto de datos que crea su script de semilla — unos 1.000 usuarios y 30.000 pedidos. Puedes ejecutarlo y verlas llegar.
orders.get_user_ordersUna refactorización se dejó la cláusula WHERE, así que cada llamada se trae la tabla entera y la filtra en Python. El reloj apenas se movió; el número de filas es la señal entera.
desde la versión v2, frente a la v1
orders.enrich_ordersUna búsqueda por lotes se convirtió en una por pedido. Cada consulta es individualmente rápida y está correctamente indexada, que es justo lo que le permite sobrevivir a la revisión y a staging.
desde la versión v2, frente a la v1
Cómo se instala
Dáselo al agente de código que ya tienes abierto. Averigua si este proyecto es Flask, Django, FastAPI o un callable WSGI pelado, pone el middleware en el sitio correcto y marca la capa de servicio — y luego arregla lo que se encuentre, en la misma conversación.
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:
pip install sixty-sh
# wsgi.py, asgi.py or main.py — before any connection is opened
import sixty
sixty.init()
# then one of these, whichever this project is:
# Flask instrument_flask(app)
# Django SixtyMiddleware, first in MIDDLEWARE
# ASGI SixtyASGIMiddleware
# WSGI SixtyMiddleware around the callable
# and the code between the view and the database
@sixty.trace
def get_user_orders(user_id):
...El identificador de versión se recoge por su cuenta de Vercel, Render, Railway, Fly, Heroku o GitHub Actions, y SIXTY_RELEASE lo fija en cualquier otro sitio. La primera comparación llega tras tu siguiente despliegue, porque no hay nada con lo que comparar una sola versión.
Qué no hace
- Un span que abarca un await comparte su hilo con lo que sea que haya ejecutado el bucle, así que reporta cero CPU en lugar de una inflada. Un servicio asyncio obtiene CPU por función síncrona y por consulta, pero no por petición — la respuesta honesta en vez de una plausible.
- No se capturan planes de consulta. Postgres está ahí y el EXPLAIN funcionaría; este agente todavía no lo emite, así que «dejó de usar su índice» llega en Node y Ruby y aquí no.
- psycopg es el único driver instrumentado. SQLAlchemy sobre psycopg se mide, porque el cursor de debajo lo está — asyncpg y los drivers de MySQL no se miden en absoluto.
- Bajo gunicorn o uwsgi cada worker reporta por separado y el colector los fusiona. Eso es correcto, y conviene saberlo cuando leas un número que describe un solo proceso.
- Aquí nada marca tus funciones por ti. Node consigue eso con una transformación de compilación y Python no tiene un gancho equivalente, así que el decorador o instrument_module es un paso que das tú — y saltárselo deja la lista con rutas y consultas y nada en medio.
Si estás aquí por una de estas
Descubre qué hizo tu último cambio.
¡Pruébalo!¿Construyes otra cosa? Mira servicios Node, servicios Go o aplicaciones Ruby y Rails — esas páginas son distintas, y también lo es lo que podemos ver.