sixty

agente de servidor

PHP

El único agente que tiene que funcionar sin un hilo en segundo plano. Un worker de PHP no puede ejecutar un temporizador y todo lo que aprendió muere con la petición, así que las ventanas se agrupan en memoria compartida y se fusionan — sin pérdidas, o los percentiles serían ficción.

paquete
sixty-sh/sixty en Packagist
funciona sobre
PHP 8.1 o posterior. Laravel 10–12, Symfony 6.4/7, Doctrine DBAL 3 y 4.
código
sixty-sh/sixty-php

Cómo instalarlo

Laravel and Symfony install themselves; anything on PDO or MongoDB takes two lines. Postgres, MySQL and MongoDB, with query plans on Postgres.

La instalación está escrita como un prompt para el agente de código que ya tienes abierto, no como una lista de tareas para ti. Es deliberado: nombra lo que tiene que ser cierto cuando la instalación esté terminada en vez de qué archivos editar, porque dónde va el código depende del framework y ponerlo en el sitio equivocado falla en silencio. Un agente puede leer tu repositorio y deducirlo; un párrafo en una página de documentación no.

El mismo texto es lo que devuelve install_sixty a través de el servidor MCP y lo que el colector sirve en /v1/setup?kind=php. Hay una sola copia.

la instalación de PHP, completa
Install the sixty agent in this PHP application so its controllers, queries
and the code between them report to sixty.

1. Add the package to the main require block of composer.json — not to
   require-dev. It runs in production; that is the entire point. Its only
   requirements are ext-json and ext-curl.

      composer require sixty-sh/sixty

2. Wire it up, which depends on the framework:

   a. Laravel — nothing to do. The service provider is discovered, and it
      registers the request span, the query instrumentation and the flush.
      Do NOT publish a config file or call Sixty::init() from a provider;
      that is a second boot path for the same thing.

   b. Symfony — add the bundle to config/bundles.php. It does the same three
      things, including the Doctrine DBAL middleware.

   c. Anything else — call \Sixty\Sixty::init() once at startup, then
      \Sixty\Instrument\Pdo::instrument($pdo) on the connection the
      application already has.

3. Measure the layer between the controller and the database. PHP has no
   build step to rewrite functions and no hook that fires when a method is
   defined, so this step is a line of code and it is the step the install
   exists for — without it the feed has routes and SQL and nothing in
   between, and an N+1 can be seen but not attributed to what causes it.

      return \Sixty\Sixty::trace('OrdersQuery#forUser', fn () => ...);

   The name is an identity compared across releases, so it must not contain
   anything that varies per call. An id in a name mints an operation per id.

4. Set these environment variables wherever the application is deployed:
      SIXTY_API_KEY  = a secret key starting sixty_sk_ — ask me for it. Do not
                       invent one, and do not commit it.
      SIXTY_SERVICE  = my-app
      SIXTY_ENDPOINT = https://ingest.sixty.sh

   The release identifier is picked up automatically on Vercel, Render,
   Railway, Fly, Heroku and GitHub Actions. If this deploys some other way,
   set SIXTY_RELEASE to the commit SHA — without one, every measurement lands
   in a single nameless bucket and no comparison can ever be made.

Constraints — correctness requirements, not style preferences:

- Do NOT change any application behaviour. This is instrumentation only: no
  refactors, no reordering of business logic, no "while I was in here" fixes.
- Do NOT write a PDO subclass or replace the application's connection. A PDO
  subclass has to open its own connection, which doubles every deployment's
  connection count silently and only fails under load. The agent sets
  PDO::ATTR_STATEMENT_CLASS on the connection that already exists; use that,
  or TracedPdo for a connection you construct yourself.
- Do NOT add any analytics, user id, session id, or cookie to what is
  reported. The agent is deliberately anonymous and must stay that way.
- If this application runs under Swoole coroutines, STOP and tell me. The
  agent refuses to enable there on purpose — requests share a worker and
  switch at every I/O boundary, so spans would be credited to the wrong
  request — and that refusal must not be worked around. FPM, CLI, RoadRunner
  and FrankenPHP are process-per-request and fully supported.

If ext-apcu is not installed, note that: with it, one request per interval
merges every worker's window and sends one payload, and without it each
request sends its own. Both are correct; the first is far less traffic.

When you are done, tell me which files you changed, so I can confirm data is
arriving.

Necesita una clave secreta — empieza por sixty_sk_ y se queda en el servidor. Genera una en la página de Ajustes cuando hayas iniciado sesión.

Qué mide

señalunidadqué significa
rowsrows per callthis query returns more rows than it used to
fanoutqueries per callthis operation now issues more database calls per invocation — an N+1
latencyms per callthis operation takes longer end to end than it used to
self_latencyms per callthe time spent in this function itself got longer — its children did not
payloadbytes per callthe serialized result of this operation got bigger
errorserror ratea larger fraction of calls are throwing
runawaycalls per minutethis operation is being called far more often than anything triggers it
repeated_querytimes per requestthe identical query runs several times within one request
overfetchrows per callfar more rows are fetched than the code appears to use
unboundedrows per callthis query has no upper bound on what it can return
recursionlevels deepthis operation calls itself, deeper than it should
new_erroroccurrencesan error that did not occur in the previous release
missing_tenancy—This reads a table of per-person data without saying whose rows it wants. Unless your database is filtering it for you, everyone gets everyone else's.
collapse—This is handing back roughly half the data it used to, or less. If that was not deliberate, something is filtering out rows that somebody expects to see.
vanished—It was being used steadily until this release and has not been used once since. Usually the link, button, or redirect that led here stopped working.
traffic_drop—This is still being used, but a fraction as often, and its share of your traffic fell too — so it is not just a quiet period.
round_tripsround trips per readone read now waits on the database many times instead of once
plan—the database chose a different plan for this query

Dónde se engancha

  • Laravel — Automático. El service provider se descubre y conecta el span de la petición, las consultas y el envío.
  • Symfony — Añade el bundle a config/bundles.php. Las mismas tres cosas, incluido el middleware de Doctrine DBAL.
  • Cualquier otra cosa — Sixty::init(), y luego Pdo::instrument($pdo) sobre la conexión que la aplicación ya tiene.
  • Tu propio código — Sixty::trace('OrdersQuery#forUser', fn () => …). PHP no tiene paso de compilación que reescriba funciones ni un gancho que se dispare cuando se define un método, así que esto es una línea en vez de un decorador.

Bases de datos

  • PDO — Instrumentado sin sustituir tu conexión: una subclase de PDO tendría que abrir la suya, duplicando en silencio el número de conexiones de cada despliegue. La clase de sentencia se fija en la conexión que ya existe.
  • Laravel, cualquier driver — Se recoge en ConnectionEstablished. Filas de rowCount().
  • Doctrine DBAL — Un middleware registrado por el bundle. Filas del propio count del resultado.
  • MongoDB — Incluido Doctrine ODM, vía la monitorización de comandos del driver. La identidad se construye solo con claves, así que ningún valor tiene camino hacia ella.

Qué hace solo este

  • Un envío que corre después de la respuesta — Primero fastcgi_finish_request(), y luego la fusión y el POST. Un colector caído que agota el tiempo de espera no le cuesta nada a quien lee, porque ya tiene su página.
  • Fusión sin pérdidas entre workers — Con ext-apcu cada petición escribe su propia ventana y una petición por intervalo las fusiona todas en una sola carga. Los sketches se unen exactamente, así que los percentiles son lo que habría reportado un único proceso midiéndolo todo.

Qué no puede hacer

  • 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 lo dice una vez al arrancar en vez de fingir lo contrario — pero es bastante más 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.
  • La CPU y la espera no se separan.

Configuración

Todos los agentes leen las mismas cuatro variables, y DRIFT_* sigue respondiendo allí donde lo hace SIXTY_* — el producto se renombró y ese nombre no es nuestro para retirarlo de los despliegues de otra gente.

SIXTY_API_KEYSin ella el agente se queda inerte y lo dice. Nunca adivina, nunca reintenta contra un endpoint desconocido, y nunca lanza una excepción.
SIXTY_SERVICECómo llamar a este servicio. Por defecto, el nombre del proyecto donde sea legible.
SIXTY_RELEASELa que más importa. Se recoge automáticamente en Vercel, Render, Railway, Fly, Heroku y GitHub Actions; en cualquier otro sitio, ponla al SHA del commit. Sin ella cada medición cae en un único cubo sin nombre y no se puede hacer ninguna comparación.
SIXTY_ENDPOINTDónde reportar. Por defecto http://localhost:4319, que es correcto en un portátil y erróneo en cuanto la aplicación se sirve a alguien más.

El resto — intervalo de envío, tasa de muestreo, qué instrumentar — está en el propio README del paquete, que es donde puede seguir siendo cierto según cambia el agente.

El agente PHP de sixty — qué mide y cómo se instala