サーバーエージェント
Python
計算と待機を区別できる唯一のエージェントです。他はどれも「コードが遅くなった」と報告してそこで止まりますが、これはそのどちらが起きたのかを言います。そして両者は正反対の対処を必要とします。
- パッケージ
sixty-sh/ PyPI- 動作環境
- Python 3.8 以降。依存パッケージなし。
- ソース
- sixty-sh/sixty-python
導入方法
Django, Flask, FastAPI or any WSGI/ASGI app: functions, HTTP routes and SQL, plus the CPU and waiting split only Python can measure.
導入手順は、あなた向けのチェックリストではなく、すでに開いているコーディングエージェント向けのプロンプトとして書かれています。これは意図的です。どのファイルを編集するかではなく、導入が終わった時点で何が成り立っていなければならないかを述べています。コードをどこに置くかはフレームワーク次第で、置き場所を間違えると静かに失敗するからです。エージェントはリポジトリを読んでそれを判断できますが、ドキュメントの段落にはできません。
同じ文面は、 install_sixty が MCP サーバー 経由で返すものであり、コレクタが次の場所で配信しているものでもあります: /v1/setup?kind=python. 実体は1つだけです。
Python の導入手順(全文)
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.秘密鍵が必要です — sixty_sk_ で始まり、サーバー側に留まります。ログイン後、設定ページで発行してください。
何を測るか
| シグナル | 単位 | 意味 |
|---|---|---|
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 |
どこに入り込むか
- Flask — instrument_flask(app) — 操作は一致した url_rule で名前が付きます。
- Django — SixtyMiddleware を MIDDLEWARE の先頭に — urls.py に書かれているとおりのルートで名前が付きます。
- FastAPI、Starlette、Litestar、Quart — SixtyASGIMiddleware、あるいは他のどんな ASGI アプリでも。
- WSGI なら何でも — Pyramid、Bottle、wsgiref、まだ誰も書いていないフレームワークでも。
- あなた自身の関数 — サービス層に @sixty.trace、あるいはモジュールまるごとなら instrument_module()。
データベース
- psycopg — バージョン2と3を、カーソルにパッチ。行数、文の形、そして呼び出した関数への帰属。
これだけができること
- 呼び出しあたりの CPU — time.thread_time() はスレッド単位で読み取りに約100ナノ秒、そして同期のスパンはその全区間で自分のスレッドを占有します — だから按分ではなく正確な数値です。
- 呼び出しあたりの待ち時間 — 計算ではなかった自分時間です。より長い処理をまたいで保持されたロック、空きのないプール、GIL を握った C 拡張。これが起きているあいだ他の呼び出し単位の数値はすべて正しいままで、だから他の何もこれを捕まえられません。
できないこと
- await をまたぐスパンは、その間ループが動かした他のものとスレッドを共有するので、水増しした CPU ではなく CPU なしとして報告します。asyncio のサービスでは同期関数ごと・クエリごとの CPU は得られますが、リクエストごとには得られません。
- クエリプランは取得しません。Postgres はそこにあり EXPLAIN も動くはずですが、エージェントはまだ発行していません。
- 計測しているドライバは psycopg だけです。psycopg の上の SQLAlchemy は下のカーソルが計測されているので測れますが、asyncpg と MySQL のドライバは測れません。
- gunicorn や uwsgi の下では各ワーカーが個別に報告し、コレクタがまとめます。これは正しい動作ですが、プロセス単位の数値を読むときには知っておく価値があります。
設定
どのエージェントも同じ4つの変数を読みます。そして SIXTY_* が答えるところでは DRIFT_* も引き続き答えます — 製品名は変わりましたが、その名前は他人のデプロイから引き上げてよい類のものではありません。
SIXTY_API_KEY | これがないとエージェントは何もせず、そのことを伝えます。推測もせず、未知のエンドポイントに再試行もせず、例外も投げません。 |
|---|---|
SIXTY_SERVICE | このサービスを何と呼ぶか。読み取れる場合はプロジェクト名が既定になります。 |
SIXTY_RELEASE | いちばん重要なもの。Vercel、Render、Railway、Fly、Heroku、GitHub Actions では自動で拾われます。それ以外ではコミットの SHA を設定してください。これがないとすべての計測が名前のない1つのバケツに入り、比較は永遠にできません。 |
SIXTY_ENDPOINT | どこへ報告するか。既定は http://localhost:4319で、ノートPCの上では正しく、そのアプリが他人に配信された瞬間に間違いになります。 |
残り — 送信間隔、サンプリング率、何を計測するか — はパッケージ自身の README にあります。エージェントが変わっても正しいままでいられる場所だからです。