agent serveur
Python
Le seul agent capable de distinguer le calcul de l’attente. Tous les autres signalent que votre code a ralenti et s’arrêtent là — celui-ci dit laquelle des deux choses s’est produite, et elles appellent des correctifs opposés.
- paquet
sixty-shsur PyPI- tourne sur
- Python 3.8 ou plus récent. Aucune dépendance.
- source
- sixty-sh/sixty-python
L’installer
Django, Flask, FastAPI or any WSGI/ASGI app: functions, HTTP routes and SQL, plus the CPU and waiting split only Python can measure.
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=python. Il n’en existe qu’une seule copie.
l’installation Python, en entier
Install the sixty agent in this Python service so its functions, HTTP routes
and database queries report to sixty.
Before editing, inspect whether this service makes LLM or agent calls. If it
does, ask the user: "Do you want LLM monitoring?" If yes, use sixty.agent,
sixty.generation and sixty.tool around custom orchestration, and call
sixty.instrument_openai(client) or sixty.instrument_anthropic(client) for
those SDKs. Never record prompts, outputs, tool arguments or tool results.
1. Add sixty-sh to this project's runtime dependencies — the same place the
web framework is declared (pyproject.toml, requirements.txt, Pipfile), not
a dev or test group. It runs in production; that is the entire point. It
has no dependencies of its own.
2. Call sixty.init() once, as early in process startup as you can get it, and
before any database connection is opened. Put it at the top of the module
the deployed process actually starts — wsgi.py, asgi.py, main.py, manage.py
for a management command — not inside a function that runs per request.
3. Wrap the application so requests become operations. Work out which of these
this project is; do exactly one:
a. Flask — call instrument_flask(app) from "sixty.instrument.flask" after
the app and its routes exist. It wraps app.wsgi_app and names operations
by the matched url_rule.
b. Django — put "sixty.instrument.django.SixtyMiddleware" FIRST in the
MIDDLEWARE list, so the span covers the rest of the middleware rather
than sitting inside it. Operations are named by the route as written in
urls.py.
c. FastAPI, Starlette, Litestar, Quart, or anything else ASGI — wrap with
SixtyASGIMiddleware from "sixty.instrument.asgi".
d. Any other WSGI application — wrap the WSGI callable with SixtyMiddleware
from "sixty.instrument.wsgi".
4. Mark the functions worth measuring. This step is what turns "this endpoint
got slow" into "this function started issuing 14 queries", and skipping it
leaves the feed with routes and queries and nothing in between:
- Put @sixty.trace on the functions that do the work — the service layer,
the repository, whatever this project calls the code between the view and
the database. Not on view functions the middleware already covers.
- Or, for a module of them, call sixty.instrument_module(sys.modules[__name__])
at the bottom of the file; it wraps every public function that module
defines and leaves imported ones alone.
- Leave anything called hundreds of thousands of times a second alone. A
span costs a couple of microseconds, which is nothing next to a request
and everything next to a tight inner loop.
5. Set these environment variables wherever the service 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 call init() at import time in a module that is also imported by test
collection or by a build step. Without a key it is inert, but a flush thread
started in a test runner is a surprise nobody asked for.
- Do NOT wrap generators or async generators with @sixty.trace. Their work
happens between next() calls, so the measurement would be of constructing an
object. Wrap whatever drains them.
- 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.
- Queries are instrumented through psycopg (2 and 3) automatically. If this
project talks to its database some other way, tell me rather than wiring
something up — measuring it may need work in the agent.
If this service runs under gunicorn, uwsgi or any pre-fork server, note how
many workers it runs: each one reports independently and the collector merges
them, which is correct, but it is worth knowing when you read the numbers.
When you are done, tell me which files you changed and what the deployed start
command now is, 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
| signal | unité | ce que cela veut dire |
|---|---|---|
rows | rows per call | this query returns more rows than it used to |
fanout | queries per call | this operation now issues more database calls per invocation — an N+1 |
latency | ms per call | this operation takes longer end to end than it used to |
self_latency | ms per call | the time spent in this function itself got longer — its children did not |
payload | bytes per call | the serialized result of this operation got bigger |
errors | error rate | a larger fraction of calls are throwing |
runaway | calls per minute | this operation is being called far more often than anything triggers it |
repeated_query | times per request | the identical query runs several times within one request |
overfetch | rows per call | far more rows are fetched than the code appears to use |
unbounded | rows per call | this query has no upper bound on what it can return |
recursion | levels deep | this operation calls itself, deeper than it should |
new_error | occurrences | an 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. |
cpu | ms of CPU per call | this function burns more processor time per call than it used to — it is doing more work, not waiting longer |
blocked | ms of waiting per call | this operation spends longer waiting for its turn while doing exactly the same amount of work |
Où il s’accroche
- Flask — instrument_flask(app) — les opérations sont nommées d’après l’url_rule correspondante.
- Django — SixtyMiddleware, en premier dans MIDDLEWARE — nommées d’après la route telle qu’écrite dans urls.py.
- FastAPI, Starlette, Litestar, Quart — SixtyASGIMiddleware, ou n’importe quelle autre application ASGI.
- Tout ce qui est WSGI — Pyramid, Bottle, wsgiref, un framework que personne n’a encore écrit.
- Vos propres fonctions — @sixty.trace sur la couche service, ou instrument_module() pour un module entier d’un coup.
Bases de données
- psycopg — Versions 2 et 3, patchées au curseur. Lignes, forme de la requête et attribution à la fonction appelante.
Ce que seul celui-ci fait
- Le CPU par appel — time.thread_time() est par thread et coûte environ 100 ns à lire, et un span synchrone possède son thread pendant toute sa durée — le chiffre est donc exact plutôt que réparti.
- L’attente par appel — Le temps propre qui n’était pas du calcul : un verrou tenu sur plus de travail, un pool sans place libre, une extension C qui garde le GIL. Tous les autres chiffres par appel restent corrects pendant que cela se produit, et c’est pourquoi rien d’autre ne l’attrape.
Ce qu’il ne peut pas faire
- Un span qui s’étend au-delà d’un await partage son thread avec tout ce que la boucle a exécuté par ailleurs : il ne remonte donc aucun CPU plutôt qu’un CPU gonflé. Un service asyncio obtient du CPU par fonction synchrone et par requête, mais pas par requête HTTP.
- Les plans de requête ne sont pas capturés. Postgres est là et l’EXPLAIN fonctionnerait ; l’agent n’en émet pas encore.
- psycopg est le seul pilote instrumenté. SQLAlchemy sur psycopg est mesuré, parce que le curseur en dessous l’est ; asyncpg et les pilotes MySQL ne le sont pas.
- Sous gunicorn ou uwsgi, chaque worker remonte indépendamment et le collecteur les fusionne. C’est correct, et bon à savoir quand vous lisez un chiffre par processus.
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_KEY | Sans 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_SERVICE | Comment appeler ce service. Par défaut le nom du projet là où il est lisible. |
SIXTY_RELEASE | La 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_ENDPOINT | Où 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.