sixty

Server-Agent

Python

Der einzige Agent, der Rechnen von Warten unterscheiden kann. Jeder andere meldet, dass dein Code langsamer geworden ist, und hört da auf — dieser sagt, welches von beidem passiert ist, und die beiden brauchen entgegengesetzte Lösungen.

Paket
sixty-sh auf PyPI
läuft auf
Python 3.8 oder neuer. Keine Abhängigkeiten.
Quellcode
sixty-sh/sixty-python

Installation

Django, Flask, FastAPI or any WSGI/ASGI app: functions, HTTP routes and SQL, plus the CPU and waiting split only Python can measure.

Die Installation ist als Prompt für den Coding-Agenten geschrieben, den du ohnehin offen hast, nicht als Checkliste für dich. Das ist Absicht: sie benennt, was am Ende wahr sein muss, statt welche Dateien zu bearbeiten sind — denn wohin der Code gehört, hängt vom Framework ab, und ihn an die falsche Stelle zu setzen scheitert lautlos. Ein Agent kann dein Repository lesen und das herausfinden; ein Absatz auf einer Dokumentationsseite kann es nicht.

Denselben Text liefert install_sixty über den MCP-Server zurück, und den serviert der Collector unter /v1/setup?kind=python. Es gibt genau eine Kopie davon.

die vollständige Python-Installation
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.

Er braucht einen geheimen Schlüssel — er beginnt mit sixty_sk_ und bleibt serverseitig. Erzeuge einen auf der Einstellungsseite, sobald du angemeldet bist.

Was er misst

SignalEinheitwas es bedeutet
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.
cpums of CPU per callthis function burns more processor time per call than it used to — it is doing more work, not waiting longer
blockedms of waiting per callthis operation spends longer waiting for its turn while doing exactly the same amount of work

Wo er sich einhängt

  • Flask — instrument_flask(app) — Operationen werden nach der gematchten url_rule benannt.
  • Django — SixtyMiddleware, als erstes in MIDDLEWARE — benannt nach der Route, wie sie in urls.py steht.
  • FastAPI, Starlette, Litestar, Quart — SixtyASGIMiddleware, oder jede andere ASGI-Anwendung.
  • Alles WSGI — Pyramid, Bottle, wsgiref, ein Framework, das noch niemand geschrieben hat.
  • Deine eigenen Funktionen — @sixty.trace auf der Service-Schicht, oder instrument_module() für ein ganzes Modul auf einmal.

Datenbanken

  • psycopg — Version 2 und 3, am Cursor gepatcht. Zeilen, Statement-Form und Zuordnung zur aufrufenden Funktion.

Was nur dieser kann

  • CPU pro Aufruf — time.thread_time() ist pro Thread und kostet etwa 100 ns zum Lesen, und ein synchroner Span besitzt seinen Thread für seine gesamte Dauer — die Zahl ist also exakt statt anteilig verteilt.
  • Warten pro Aufruf — Eigenzeit, die nicht Rechnen war: ein Lock über mehr Arbeit gehalten, ein Pool ohne freien Platz, eine C-Erweiterung, die das GIL hält. Jede andere Zahl pro Aufruf bleibt korrekt, während das passiert — deshalb fängt es nichts sonst.

Was er nicht kann

  • Ein Span, der über ein await hinweg besteht, teilt seinen Thread mit allem anderen, was die Loop ausgeführt hat, er meldet also keine CPU statt einer aufgeblähten. Ein asyncio-Dienst bekommt CPU pro synchroner Funktion und pro Abfrage, aber nicht pro Anfrage.
  • Abfragepläne werden nicht erfasst. Postgres ist da und das EXPLAIN würde funktionieren; der Agent setzt noch keines ab.
  • psycopg ist der einzige instrumentierte Treiber. SQLAlchemy auf psycopg wird gemessen, weil der Cursor darunter es wird; asyncpg und die MySQL-Treiber nicht.
  • Unter gunicorn oder uwsgi meldet jeder Worker eigenständig und der Collector führt sie zusammen. Das ist korrekt, und man sollte es wissen, wenn man eine Zahl pro Prozess liest.

Konfiguration

Jeder Agent liest dieselben vier Variablen, und DRIFT_* antwortet weiterhin überall dort, wo SIXTY_* es tut — das Produkt wurde umbenannt, und dieser Name ist nicht unserer, um ihn aus fremden Deployments zu entfernen.

SIXTY_API_KEYOhne sie bleibt der Agent untätig und sagt das auch. Er rät nie, versucht es nie erneut gegen einen unbekannten Endpunkt, und wirft nie.
SIXTY_SERVICEWie dieser Dienst heißen soll. Standardmäßig der Projektname, wo einer lesbar ist.
SIXTY_RELEASEDie wichtigste. Wird auf Vercel, Render, Railway, Fly, Heroku und GitHub Actions automatisch abgeholt; überall sonst setze sie auf den Commit-SHA. Ohne sie landet jede Messung in einem einzigen namenlosen Eimer, und kein Vergleich ist je möglich.
SIXTY_ENDPOINTWohin gemeldet wird. Standardmäßig http://localhost:4319, was auf einem Laptop richtig ist und in dem Moment falsch, in dem die Anwendung jemand anderem ausgeliefert wird.

Der Rest — Flush-Intervall, Sample-Rate, was instrumentiert wird — steht im README des Pakets selbst, wo es wahr bleiben kann, während sich der Agent verändert.

Der Python-Agent von sixty — was er misst und wie man ihn installiert