sixty

agent serveur

PHP

Le seul agent qui doit fonctionner sans thread de fond. Un worker PHP ne peut pas faire tourner de minuterie et tout ce qu’il a appris meurt avec la requête : les fenêtres sont donc mises en commun en mémoire partagée et fusionnées — sans perte, sinon les percentiles seraient de la fiction.

paquet
sixty-sh/sixty sur Packagist
tourne sur
PHP 8.1 ou plus récent. Laravel 10–12, Symfony 6.4/7, Doctrine DBAL 3 et 4.
source
sixty-sh/sixty-php

L’installer

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

L’installation est écrite comme un prompt pour l’agent de code que vous avez déjà ouvert, pas comme une liste de tâches pour vous. C’est délibéré : elle nomme ce qui doit être vrai une fois l’installation terminée plutôt que les fichiers à modifier, parce que l’endroit où va le code dépend du framework et que le mettre au mauvais endroit échoue silencieusement. Un agent peut lire votre dépôt et le déduire ; un paragraphe sur une page de documentation, non.

Le même texte est ce que renvoie install_sixty via le serveur MCP et ce que le collecteur sert à /v1/setup?kind=php. Il n’en existe qu’une seule copie.

l’installation PHP, en entier
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.

Il lui faut une clé secrète — elle commence par sixty_sk_ et reste côté serveur. Générez-en une sur la page Réglages une fois connecté.

Ce qu’il mesure

signalunitéce que cela veut dire
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

Où il s’accroche

  • Laravel — Automatique. Le service provider est découvert et branche le span de requête, les requêtes SQL et l’envoi.
  • Symfony — Ajoutez le bundle à config/bundles.php. Les mêmes trois choses, y compris le middleware Doctrine DBAL.
  • Tout le reste — Sixty::init(), puis Pdo::instrument($pdo) sur la connexion que l’application a déjà.
  • Votre propre code — Sixty::trace('OrdersQuery#forUser', fn () => …). PHP n’a pas d’étape de build qui réécrit les fonctions ni de point d’accroche qui se déclenche à la définition d’une méthode : c’est donc une ligne plutôt qu’un décorateur.

Bases de données

  • PDO — Instrumenté sans remplacer votre connexion : une sous-classe de PDO devrait ouvrir la sienne, doublant silencieusement le nombre de connexions de chaque déploiement. La classe de statement est posée sur la connexion qui existe déjà.
  • Laravel, n’importe quel pilote — Récupéré à ConnectionEstablished. Lignes depuis rowCount().
  • Doctrine DBAL — Un middleware enregistré par le bundle. Lignes depuis le count du résultat lui-même.
  • MongoDB — Y compris Doctrine ODM, via la surveillance des commandes du pilote. L’identité est construite à partir des clés uniquement : aucune valeur n’a de chemin vers elle.

Ce que seul celui-ci fait

  • Un envoi qui a lieu après la réponse — fastcgi_finish_request() d’abord, puis la fusion et le POST. Un collecteur en panne qui va au bout du timeout ne coûte rien au lecteur, puisqu’il a déjà sa page.
  • Une fusion sans perte entre workers — Avec ext-apcu, chaque requête écrit sa propre fenêtre et une requête par intervalle les fusionne toutes en une seule charge utile. Les sketches s’unissent exactement : les percentiles sont donc ceux qu’aurait remontés un processus unique mesurant tout.

Ce qu’il ne peut pas faire

  • Il refuse de s’activer sous les coroutines Swoole, et dit pourquoi. Là-bas, plusieurs requêtes partagent un worker et basculent à chaque frontière d’E/S : le span courant créditerait les requêtes de l’une au contrôleur d’une autre. FPM, CLI, RoadRunner et FrankenPHP sont un processus par requête et sont pleinement pris en charge.
  • Sans ext-apcu, chaque requête envoie sa propre fenêtre. Cela fonctionne toujours, et l’agent le dit une fois au démarrage plutôt que de faire semblant — mais c’est beaucoup plus de trafic.
  • PDO::query() et PDO::exec() n’atteignent jamais une classe de statement : une connexion que vous construisez vous-même et sur laquelle vous les appelez a besoin de TracedPdo. prepare() + execute() — chaque requête émise par Laravel et Doctrine — est couvert.
  • Si quelque chose d’autre possède déjà la classe de statement — un profileur, une barre de débogage — l’agent la laisse tranquille et ne mesure rien plutôt que de la casser.
  • Le CPU et l’attente ne sont pas séparés.

Configuration

Chaque agent lit les mêmes quatre variables, et DRIFT_* répond toujours partout où SIXTY_* répond — le produit a été renommé, et ce nom ne nous appartient pas au point de le retirer des déploiements des autres.

SIXTY_API_KEYSans elle l’agent reste inerte et le dit. Il ne devine jamais, ne réessaie jamais contre un point d’entrée inconnu, et ne lève jamais d’exception.
SIXTY_SERVICEComment appeler ce service. Par défaut le nom du projet là où il est lisible.
SIXTY_RELEASELa plus importante. Récupérée automatiquement sur Vercel, Render, Railway, Fly, Heroku et GitHub Actions ; partout ailleurs, mettez-y le SHA du commit. Sans elle, chaque mesure atterrit dans un unique seau sans nom et aucune comparaison n’est jamais possible.
SIXTY_ENDPOINTOù remonter. Par défaut http://localhost:4319, ce qui est juste sur un portable et faux dès l’instant où l’application est servie à quelqu’un d’autre.

Le reste — intervalle d’envoi, taux d’échantillonnage, quoi instrumenter — est dans le README du paquet lui-même, là où il peut rester vrai à mesure que l’agent change.

L’agent PHP de sixty — ce qu’il mesure et comment l’installer