sixty

Server-Agent

Ruby & Rails

Die einzige Installation ohne zweiten Schritt. Das Railtie fügt die Middleware hinzu, abonniert sql.active_record und umschließt Controller-Actions beim Start — es gibt keinen Initializer zu schreiben.

Paket
sixty auf RubyGems
läuft auf
Ruby 2.7 oder neuer. Rails 6.1+ installiert sich selbst; für den Rest genügt Rack.
Quellcode
sixty-sh/sixty-ruby

Installation

Rails installs itself — the railtie adds the middleware, subscribes to sql.active_record and wraps controllers on boot. Postgres, MySQL and MongoDB, with query plans on Postgres.

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=ruby. Es gibt genau eine Kopie davon.

die vollständige Ruby & Rails-Installation
Install the sixty agent in this Ruby application so its controllers, service
objects and database queries report to sixty.

1. Add the gem to the main group of the Gemfile — not to :development and not
   to :test. It runs in production; that is the entire point. It has no
   dependencies of its own, so nothing else in the lockfile moves.

      gem 'sixty'

   Then bundle install.

2. Rails needs no further wiring. The railtie adds the middleware, subscribes
   to sql.active_record and wraps controller actions on boot. Do NOT write an
   initializer that calls Sixty.init — in a Rails app that is a second boot
   path for the same thing.

   If this is Sinatra, Hanami, a bare Rack app or a non-web process, then and
   only then do it by hand: require 'sixty' and call Sixty.init at startup,
   and add "use Sixty::Instrument::Rack" to config.ru.

3. Measure the layer between the controller and the database. This 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 the object that
   causes it. In a Rails app that object is a query object, a service object,
   a serializer or a job — rarely the controller.

      class OrdersQuery
        include Sixty::Instrumented
      end

   Every public instance method of that class becomes an operation, with its
   queries and rows attributed to it. Private methods and plain accessors are
   skipped. Add it to the few classes that do real work per request — not to
   models, and not to anything called in a tight loop.

   For a class you do not own: Sixty.instrument(Stripe::Charge, :create)
   For a block: Sixty.trace('nightly-reconciliation') { ... }

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 include Sixty::Instrumented in ActiveRecord models or in anything
  called per row. A span costs a few microseconds, which is nothing next to a
  request and everything next to a loop over a result set.
- 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.
- Leave the database configuration alone. pg, mysql2, trilogy and mongo are
  all instrumented already; if this application reaches its database some
  other way, tell me rather than wiring something up.

If this runs under Puma with workers, or under Sidekiq, note how many
processes it runs: each 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, 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.
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

Wo er sich einhängt

  • Rails — Automatisch. Controller werden zu Operationen, benannt nach controller#action.
  • Rack — use Sixty::Instrument::Rack in config.ru, für Sinatra, Hanami oder eine nackte Anwendung.
  • Deine eigenen Klassen — include Sixty::Instrumented — jede öffentliche Instanzmethode wird zu einer Operation. Private Methoden und Accessoren bleiben unberührt.
  • Alles andere — Sixty.instrument(Stripe::Charge, :create), oder Sixty.trace("job") { }.

Datenbanken

  • pg — Zeilen, Statement-Form und Abfragepläne über EXPLAIN.
  • mysql2 / trilogy — Gelesen nach MySQLs Quoting-Regeln, nicht nach denen von Postgres — eine Zeichenkette in doppelten Anführungszeichen ist im einen ein Literal und im anderen ein Bezeichner.
  • mongo — Was Mongoid benutzt. Befehlsform als Identität.

Was nur dieser kann

  • Nichts zu schreiben — Rails hat einen dokumentierten Boot-Hook und einen Event-Bus, der ohnehin jede Abfrage bekannt gibt — das Gem braucht also weder einen Initializer noch einen Wrapper.
  • Abfragepläne auf Postgres — Dasselbe EXPLAIN wie beim Node-Agenten, pro Statement gecacht.

Was er nicht kann

  • Abfragepläne gibt es nur für Postgres. MySQL hat kein EXPLAIN mit generischem Plan, und MongoDB hat kein Statement zum Erklären.
  • CPU und Warten werden nicht getrennt.
  • Unter Puma mit Workern oder unter Sidekiq meldet jeder Prozess eigenständig und der Collector führt sie zusammen.

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 Ruby & Rails-Agent von sixty — was er misst und wie man ihn installiert