DeadBro Documentation
New here? Follow the getting started guide for setup, choosing monitors, and your first debugging workflow.
DeadBro monitors Rails request performance, N+1 queries, and errors. Add the gem, configure your API key, and send traffic to start exploring captured requests. Additional capabilities depend on your plan.
Installation
Add dead_bro to your Gemfile. It works with Rails 6.1+ and Ruby 3.0+.
# Gemfile gem 'dead_bro', git: 'https://github.com/rubydevro/dead_bro.git'
Then run the installer. It writes a minimal initializer and tells you where to add your API key.
bundle install bin/rails generate dead_bro:install
Get your API key from the DeadBro dashboard on your application page. One key per application — environments are detected automatically from Rails.env.
The installer creates config/initializers/dead_bro.rb with sensible defaults. Set DEAD_BRO_API_KEY in your environment, then restart or deploy your app. Send a few requests to confirm data is arriving.
Configuration
The only required setting is api_key. Everything else has a safe default and most settings can be adjusted remotely from the dashboard without redeploying.
DeadBro.configure do |c| c.api_key = Rails.application.credentials.dead_bro_api_key # or: ENV["DEAD_BRO_API_KEY"] end
Never hardcode your API key. Use Rails credentials or ENV["DEAD_BRO_API_KEY"]. Treat it like a database password.
Remote-managed settings
Most tuning happens in the dashboard, not in code. DeadBro receives updated settings in API responses, so you can change the sample rate and add exclusions without touching your codebase. Plan-only EXPLAIN requires a local opt-in, and the dashboard can turn it off.
| Setting | Default | Description |
|---|---|---|
| sample_rate | 100 | Requested percentage of requests to instrument; your plan caps it at 25% on Free, 50% on Starter, or 100% on Growth and above. Errors are captured regardless of this setting. |
| slow_query_threshold_ms | 500 | Queries slower than this get a backtrace attached so you can see exactly which line of code triggered them. |
| max_sql_queries_to_send | 500 | Upper limit on SQL query details sent per request. Prevents oversized payloads on very chatty pages. |
| max_logs_to_send | 100 | Maximum captured log lines attached per request. |
| memory_tracking_enabled | true | Enables lightweight memory growth measurement. |
| allocation_tracking_enabled | false | Enables detailed object allocation snapshots. Enable temporarily for deep memory investigations, as this adds overhead. |
| explain_enabled | false | Requires dead_bro ≥ 0.2.33; on 0.2.32 or older, use config.explain_analyze_enabled = true instead. Captures a plan-only EXPLAIN for slow SELECT queries (statements are never executed). Local opt-in: must be set in your app's config; the dashboard toggle can only disable it remotely. |
| excluded_controllers | [] | Controller patterns to skip entirely. Supports wildcards, e.g. ["HealthController", "Assets*"]. |
| excluded_jobs | [] | Job class patterns to skip. |
Environments
DeadBro detects the current environment automatically from Rails.env and tags all data accordingly. You use a single production API key — no separate keys per environment, no extra config.
Data is segmented by environment in the dashboard so you can filter to production, staging, or development independently. To adjust remote settings for an environment, use the dashboard controls. Local-only settings still require a deploy.
After deploying, make a few requests and check the Live indicator on your application in the dashboard. Captured data should appear after your app sends a few requests.
N+1 Detection
DeadBro instruments ActiveRecord via Rails notifications and automatically identifies when the same SQL query pattern fires more than once per request. No annotation or opt-in required — captured requests are analyzed, subject to your plan’s sampling limit.
An N+1 is flagged when the same parameterized query runs 5 or more times within a single request. The dashboard shows the repeated SQL, its total execution count, and the backtrace line so you can add the right includes() call immediately.
N+1 alerts include the offending backtrace line, the repeated SQL pattern, total execution count, and a suggested includes() fix — right in the request detail view.
Error Tracking
All unhandled exceptions are captured automatically with full request context: params, headers, session data, user info, SQL queries that ran before the crash, and the complete backtrace. Errors bypass sample rate — they are always sent.
What's included in every error report
Manual Error Tracking
Automatic error capture covers unhandled exceptions. DeadBro.track covers everything else — errors your app handles gracefully and never re-raises.
begin charge_card(user) rescue Stripe::CardError => e DeadBro.track(e, user_id: current_user.id, plan: "pro") render json: { error: "Card declined" }, status: 422 end
Any keyword arguments you pass are recorded as a context block in the error detail view — useful for attaching record IDs, plan names, feature flags, or anything else that helps reproduce the problem.
What's included in a tracked error
DeadBro.track never raises and never interferes with your rescue block. If the DeadBro backend is unreachable the call is a silent no-op. Tracked errors bypass sample rate and are always sent.
Distinguishing tracked vs unhandled errors
Tracked errors are tagged tracked: true in the payload so you can filter them separately in the dashboard. Unhandled exceptions captured automatically by the middleware are tagged tracked: false (the default).
Performance Monitoring
Every instrumented request records a full timing breakdown. The dashboard aggregates these into P50 / P90 / P99 percentile charts and per-endpoint tables so you can see which actions are slow and why.
Outgoing HTTP tracking
DeadBro instruments Net::HTTP automatically. Every outbound HTTP call — Stripe, Twilio, internal services — is tracked with URL, method, response status, duration, and response size.
Memory Tracking
DeadBro measures memory at two levels. Lightweight mode is on by default. Detailed allocation tracking can be enabled temporarily when hunting a specific leak; measure its overhead in your own app.
Lightweight mode (default)
Captures a GC stats snapshot before and after each request. From the diff, DeadBro computes memory growth in MB, GC run count increase, and Ruby heap expansion. A request is flagged with a :memory issue when growth exceeds 100 MB.
GC pressure tracking
Beyond memory growth, DeadBro tracks garbage collector pressure per request: minor GC runs, major GC runs, and cumulative GC time. High GC pressure from a specific endpoint signals excessive short-lived object creation.
Detailed allocation tracking adds 2–5ms overhead. Enable it for targeted investigations, then turn it off from the dashboard.
SQL Query Analysis
DeadBro records every SQL query that runs during a request: sanitized query text, model name, duration, and whether the result came from the query cache. Slow queries get a backtrace so you can trace them back to your code.
EXPLAIN plans
When enabled (off by default), DeadBro captures a plan-only EXPLAIN for SELECT queries exceeding slow_query_threshold_ms. The query plan appears in the SQL Queries section of the dashboard.
Safe by design: DeadBro never uses EXPLAIN ANALYZE, so your queries are never re-executed and your data is never modified. Only SELECT statements are explained, quoted values are scrubbed from plan text, and the feature must be opted into in your app's own config — the dashboard toggle can disable it remotely but never enable it.
Database connection tracking
Beyond individual queries, DeadBro tracks connection pool activity: checkouts per request and time spent waiting for a connection. High wait times indicate your connection pool is undersized for your concurrency.
Background Jobs
DeadBro tracks ActiveJob background jobs with the same depth as HTTP requests. Every job execution — success or failure — is recorded with full context.
Excluding jobs
DeadBro.configure do |c| c.api_key = ENV["DEAD_BRO_API_KEY"] c.excluded_jobs = ["HeartbeatJob", "Maintenance*"] end
ActiveRecord Object Tracking
DeadBro counts the number of ActiveRecord objects instantiated during each request and job. This reveals over-fetching patterns that cost memory and CPU even when they don't show up as N+1 queries.
A classic example: loading all columns of a model when you need two, then iterating 10,000 rows. The query runs once (no N+1), but Ruby instantiates 10,000 objects and GC pays the price.
The ar_heavy flag
A request is flagged :ar_heavy when its AR instantiation count reaches 10,000 objects. These surface in Issues so you can add .select(), tighten scopes, or switch to pluck.
Cache & Redis Tracking
DeadBro instruments Rails cache operations and Redis calls automatically. Cache hit/miss rates and individual operation timing are recorded alongside the request so you can see the full picture in one place.
Every Rails.cache read, write, fetch, and delete is tracked. Redis operations (when used directly or via Sidekiq) are tracked with operation type, key pattern, and duration. Elasticsearch and OpenSearch queries are also tracked when your app uses the official client.
Logs
DeadBro captures application log lines as they're written during a request and attaches them to the request record. No more digging through log files — see exactly what your app logged during a specific slow or failed request.
Up to max_logs_to_send lines per request (default: 100). The Logs section in the dashboard lets you browse logs by severity and jump from any log line directly to the request that produced it.
Logs are available on the Growth plan and above. Error-level logs are surfaced on the account dashboard regardless of plan tier.
Console: DeadBro.analyze
DeadBro.analyze is a Rails console tool that profiles any block of code — measuring its wall-clock time, SQL queries, and memory usage — and returns a structured result you can inspect immediately. It is designed for debug sessions: it never sends data to the backend and adds no overhead to running requests.
result = DeadBro.analyze("load active users") do User.where(active: true).includes(:profile).to_a end #=> #<DeadBro::AnalysisResult label="load active users" total_time_ms=42.1 sql_count=2 sql_time_ms=38.4 memory_before_mb=312.1 memory_after_mb=314.8 memory_delta_mb=2.7>
The console echoes the result automatically. The inspect line shows the headline numbers — SQL query details are intentionally omitted to keep it readable when queries are numerous.
verbose mode
Pass verbose: true to lower Rails' log level to DEBUG and enable ActiveRecord.verbose_query_logs for the duration of the block. This causes Rails' own SQL logging to appear inline with ↳ caller/frame.rb:12 annotations so you can trace each query back to the line of code that triggered it.
result = DeadBro.analyze("checkout flow", verbose: true) do Order.find(42).line_items.map(&:product) end
Result fields
Drilling into SQL
sql_queries is excluded from inspect but always accessible. Each entry is a hash with the normalized query, its type, how many times it ran, and its cumulative time. Two shortcuts surface the most interesting patterns immediately:
result.most_queries # top 5 patterns by execution count — spot N+1s result.longest_queries # top 5 patterns by cumulative time — spot slow queries result.sql_queries # full list — {sql:, query_type:, count:, total_time_ms:}
Memory details and automatic warnings
result.memory_details is a hash with GC and object-level data. DeadBro raises automatic warnings inside it when it detects meaningful signals:
result.memory_details
#=> {
# gc_collections: 3, # GC runs triggered by the block
# heap_pages_added: 0, # Ruby heap pages allocated from the OS
# new_objects: 14_820, # objects created during the block
# object_breakdown: { # per-type object delta (top 20, noise filtered)
# "String" => 8100,
# "Array" => 2400,
# ...
# },
# large_objects: [], # objects >1 MB found in ObjectSpace
# warnings: [ # automatic signals worth investigating
# "GC ran 3 times — many short-lived objects being created"
# ]
# }
Warnings fire when: memory grows more than 20 MB, GC runs more than 5 times, the Ruby heap grows by more than 10 pages, or objects larger than 1 MB are found in ObjectSpace.
DeadBro.analyze performs a full ObjectSpace walk to find large objects. This is safe in a console session but should never be called in a request or job in production — the scan is intentionally unbounded and will block the process.
Dashboard — Overview
The account-level dashboard gives you a cross-application view of everything happening right now.
Dashboard — Request Explorer
The Request Explorer lets you browse every instrumented request, grouped by endpoint (controller#action). It's split into tabs so you can navigate from aggregate stats to individual samples in one click.
Inside a request type
- Performance — Response time chart, memory usage, SQL count, DB time, percentile breakdown
- Samples — Individual request records with duration, SQL count, memory, and issue flags per row
- Errors — Same as Samples but filtered to requests that raised an exception
- SQL — Query patterns aggregated across all samples for this endpoint
Opening an individual request shows the complete profile: timing breakdown, all SQL queries, outgoing HTTP calls, memory stats, captured logs, and (if an error) the full backtrace. Use Compare to open two requests side-by-side and diff their SQL patterns.
Dashboard — Issues
The Issues section surfaces requests that stand out as anomalies — automatically, with no configuration required. DeadBro compares each request against the baseline for its endpoint and flags it if something looks unusual.
Slow request
Duration significantly exceeds the baseline. Thresholds adapt: a 300ms endpoint gets flagged at 1s; a 1s endpoint at 3s.
Memory spike
Request caused more than 100 MB of memory growth. Often from loading large datasets into Ruby objects or returning huge JSON responses.
N+1 query pattern
The same SQL pattern executed 5 or more times. Add includes() or preload() to fix it.
High query count
More than 10 SQL queries in a single request — often missing eager loading or logic that should be one query.
Excessive AR instantiation
10,000+ ActiveRecord objects instantiated. Consider .select(), .pluck(), or pagination.
Alarms
Each endpoint can have an alarm with a custom duration threshold and alerting mode — from every occurrence to first in deploy to never notify. Alarm state (open / work in progress / closed) lets your team track remediation directly in the dashboard.
Dashboard — Timeline
The Timeline view shows requests chronologically for post-incident analysis. Pick a time window around an outage and see exactly which requests were slow or erroring, alongside the deploy events that bracket them.
Each row shows the request path, duration, SQL count, memory usage, and issue flags at a glance. Error rows are highlighted so they stand out without filtering.
Slack Alerts
Connect DeadBro to your Slack workspace to receive real-time alerts when alarms fire, errors spike, or new insights are detected. Each application can post to a different channel.
Connect your workspace
Go to Settings → Integrations → Slack and click Add to Slack. DeadBro only requests permission to post messages — it doesn't read your messages.
Add alert endpoints
After connecting, add one or more channel endpoints per application. Route errors to #eng-alerts and performance issues to #perf-review, for example.
Configure alarm thresholds
Set thresholds per request type in the Request Explorer. When a request exceeds the threshold, a notification fires to your configured channels.
Deploy Tracking
DeadBro links every request to the deployment that served it. On startup, the gem resolves a revision ID from (in order): your REVISION file, common platform env vars (HEROKU_SLUG_COMMIT, RENDER_GIT_COMMIT, RAILWAY_GIT_COMMIT_SHA), or a per-process UUID as fallback.
Deploy markers appear on performance charts so you can correlate timeline spikes with releases visually. Available on the Starter plan and above.
Changelog
- New:
DeadBro.track(error, **context)— manually report handled exceptions from anyrescueblock with optional keyword context
- New:
ArObjectTracker— counts ActiveRecord instantiations per request/job, surfaced as the:ar_heavyissue flag (threshold: 10,000 objects) - New: Memory usage column and multi-flag performance badges on request tables
- Fix: Replaced deprecated
to_s(:delimited)withnumber_with_delimiterfor Rails 7.2+ compatibility - Fix: Narrowed bare
rescuetorescue StandardErrorinArObjectTracker.subscribe!
- New: GC pressure tracking — records minor/major GC runs and GC time per request and job
- New:
GcTrackerintegrated into both Rack middleware and JobSubscriber - Improved:
ApmRequest#sample_breakdownnow includes GC pressure data
- New: Lightweight memory tracking mode
- New: Optional detailed allocation tracking, toggleable from dashboard
- New:
:memoryissue flag for requests with >100 MB memory growth
- New: Background job tracking via
JobSubscriber - New: Redis and Elasticsearch operation tracking
- New: Database connection pool wait time tracking
- New: Outgoing HTTP instrumentation via Net::HTTP
- New: Circuit breaker to protect your app when DeadBro is unreachable
- Initial release
- Request tracking, N+1 detection, error capture, SQL query recording
- View rendering instrumentation, Rails cache hit/miss tracking, deploy correlation