DeadBro (Beta Version)
Minimal APM for Rails apps. Automatically measures each controller action's total time, tracks SQL queries, monitors view rendering performance, tracks memory usage and detects leaks, monitors background jobs, and posts metrics to a remote endpoint with an API key read from your app's settings/credentials/env.
To use the gem you need to have a free account with DeadBro - Rails APM
Installation
Add to your Gemfile:
gem "dead_bro", git: "https://github.com/rubydevro/dead_bro.git"Usage
By default, if Rails is present, DeadBro auto-subscribes to process_action.action_controller and posts metrics asynchronously.
Configuration settings
You can set via an initializer:
DeadBro.configure do |config|
config.api_key = ENV["DEAD_BRO_API_KEY"]
config.enabled = true
endRequest Sampling
DeadBro supports configurable request sampling to reduce the volume of metrics sent to your APM endpoint, which is useful for high-traffic applications.
Configuration
Set the sample rate as a percentage (1-100):
# Track 50% of requests
DeadBro.configure do |config|
config.sample_rate = 50
end
# Track 10% of requests (useful for high-traffic apps)
DeadBro.configure do |config|
config.sample_rate = 10
end
# Track all requests (default)
DeadBro.configure do |config|
config.sample_rate = 100
endHow It Works
- Random Sampling: Each request has a random chance of being tracked based on the sample rate
- Consistent Per-Request: The sampling decision is made once per request and applies to all metrics for that request
- Debug Logging: Skipped requests do not count towards the montly limit
- Error Tracking: Errors are still tracked regardless of sampling
Use Cases
- High-Traffic Applications: Reduce APM data volume and costs
- Development/Staging: Sample fewer requests to reduce noise
- Performance Testing: Track a subset of requests during load testing
- Cost Optimization: Balance monitoring coverage with data costs
Excluding Controllers and Jobs
You can exclude specific controllers and jobs from APM tracking.
Configuration
DeadBro.configure do |config|
config.excluded_controllers = [
"HealthChecksController",
"Admin::*" # wildcard supported
]
config.excluded_controller_actions = [
"UsersController#show",
"Admin::ReportsController#index",
"Admin::*#*" # wildcard supported for controller and action
]
config.excluded_jobs = [
"ActiveStorage::AnalyzeJob",
"Admin::*"
]
endNotes:
- Wildcards
*are supported for controller and action (e.g.,Admin::*#*). - Matching is done against full names like
UsersController,Admin::ReportsController#index,MyJob.
Exclusive Tracking (Whitelist Mode)
You can configure DeadBro to only track specific controllers, actions, or jobs. This is useful when you want to focus monitoring on a subset of your application.
Configuration
DeadBro.configure do |config|
# Only track these specific controller actions
config.exclusive_controller_actions = [
"UsersController#show",
"UsersController#index",
"Admin::ReportsController#*", # all actions in this controller
"Api::*#*" # all actions in all Api controllers
]
# Only track these specific jobs
config.exclusive_jobs = [
"PaymentProcessingJob",
"EmailDeliveryJob",
"Admin::*" # all jobs in Admin namespace
]
endHow It Works
-
If
exclusive_controller_actionsorexclusive_jobsis empty/not defined: All controllers/actions/jobs are tracked (default behavior) -
If
exclusive_controller_actionsorexclusive_jobsis defined with values: Only matching controllers/actions/jobs are tracked -
Exclusion takes precedence: If something is in both
excluded_*andexclusive_*, it will be excluded (exclusion is checked first)
Use Cases
- Focus on Critical Paths: Monitor only your most important endpoints
- Cost Optimization: Track only specific high-value operations
- Debugging: Temporarily focus on specific controllers/jobs during investigation
- Compliance: Track only operations that require monitoring for compliance reasons
Environment Variables
You can also configure exclusive tracking via environment variables:
# Comma-separated list of controller#action patterns
dead_bro_EXCLUSIVE_CONTROLLER_ACTIONS="UsersController#show,Admin::*#*"
# Comma-separated list of job patterns
dead_bro_EXCLUSIVE_JOBS="PaymentProcessingJob,EmailDeliveryJob"SQL Query Tracking
DeadBro automatically tracks SQL queries executed during each request and job. Each request will include a sql_queries array containing:
-
sql- The SQL query (always sanitized) -
name- Query name (e.g., "User Load", "User Update") -
duration_ms- Query execution time in milliseconds -
cached- Whether the query was cached -
connection_id- Database connection ID -
trace- Call stack showing where the query was executed -
explain_plan- Query execution plan (when EXPLAIN ANALYZE is enabled, see below)
Automatic EXPLAIN ANALYZE for Slow Queries
DeadBro can automatically run EXPLAIN ANALYZE on slow SQL queries to help you understand query performance and identify optimization opportunities. This feature runs in the background and doesn't block your application requests.
How It Works
- Automatic Detection: When a query exceeds the configured threshold, DeadBro automatically captures its execution plan
- Background Execution: EXPLAIN ANALYZE runs in a separate thread using a dedicated database connection, so it never blocks your application
- Database Support: Works with PostgreSQL, MySQL, SQLite, and other databases
- Smart Filtering: Automatically skips transaction queries (BEGIN, COMMIT, ROLLBACK) and other queries that don't benefit from EXPLAIN
Configuration
-
explain_analyze_enabled(default:false) - Set totrueto enable automatic EXPLAIN ANALYZE -
slow_query_threshold_ms(default:500) - Queries taking longer than this threshold will have their execution plan captured
Example Configuration
DeadBro.configure do |config|
config.api_key = ENV['DEAD_BRO_API_KEY']
config.enabled = true
# Enable EXPLAIN ANALYZE for queries slower than 500ms
config.explain_analyze_enabled = true
config.slow_query_threshold_ms = 500
# Or use a higher threshold for production
# config.slow_query_threshold_ms = 1000 # Only explain queries > 1 second
endWhat You Get
When a slow query is detected, the explain_plan field in the SQL query data will contain:
- PostgreSQL: Full EXPLAIN ANALYZE output with buffer usage statistics
- MySQL: EXPLAIN ANALYZE output showing actual execution times
- SQLite: EXPLAIN QUERY PLAN output
- Other databases: Standard EXPLAIN output
This execution plan helps you:
- Identify missing indexes
- Understand query execution order
- Spot full table scans
- Optimize JOIN operations
- Analyze buffer and cache usage (PostgreSQL)
View Rendering Tracking
DeadBro automatically tracks view rendering performance for each request. This includes:
- Individual view events: Templates, partials, and collections rendered
- Performance metrics: Rendering times for each view component
- Cache analysis: Cache hit rates for partials and collections
- Slow view detection: Identification of the slowest rendering views
- Frequency analysis: Most frequently rendered views
Memory Tracking & Leak Detection
DeadBro automatically tracks memory usage and detects memory leaks with minimal performance impact. This includes:
Performance-Optimized Memory Tracking
By default, DeadBro uses lightweight memory tracking that has minimal performance impact:
- Memory Usage Monitoring: Track memory consumption per request (using GC stats, not system calls)
- Memory Leak Detection: Detect growing memory patterns over time
- GC Efficiency Analysis: Monitor garbage collection effectiveness
- Zero Allocation Tracking: No object allocation tracking by default (can be enabled)
Configuration Options
# In your Rails configuration
DeadBro.configure do |config|
config.memory_tracking_enabled = true # Enable lightweight memory tracking (default: true)
config.allocation_tracking_enabled = false # Enable detailed allocation tracking (default: false)
# Sampling configuration
config.sample_rate = 100 # Percentage of requests to track (1-100, default: 100)
endPerformance Impact:
- Lightweight mode: ~0.1ms overhead per request
- Allocation tracking: ~2-5ms overhead per request (only enable when needed)
Job Tracking
DeadBro automatically tracks ActiveJob background jobs when ActiveJob is available. Each job execution is tracked with:
-
job_class- The job class name (e.g., "UserMailer::WelcomeEmail") -
job_id- Unique job identifier -
queue_name- The queue the job was processed from -
arguments- Sanitized job arguments (sensitive data filtered) -
duration_ms- Job execution time in milliseconds -
status- "completed" or "failed" -
sql_queries- Array of SQL queries executed during the job -
exception_class- Exception class name (for failed jobs) -
message- Exception message (for failed jobs) -
backtrace- Exception backtrace (for failed jobs)
Control Plane Metrics (Queues, DB, Process, System)
DeadBro includes a lightweight control plane metrics job that runs periodically (by default once per minute via DeadBro::JobQueueMonitor) and sends a single JSON payload summarizing:
-
Sidekiq / job queues: global stats (
processed,failed,enqueued,scheduled_size,retry_size,dead_size,workers_size,processes_size), and per-queue entries withname,size, andlatency_s. -
Database (best effort): connection pool stats and a simple
ping_mslatency whenActiveRecordis available and connected. -
Process / Rails:
pid,hostname, uptime, Ruby/Rails versions, environment, GC stats, RSS (rss_bytes), thread and file descriptor counts (on Linux). - System (Linux best effort): CPU percentage over the last interval (normalised 0–100), memory used/total/available, plus filesystem and network summaries.
Everything is best effort and designed to be safe and low overhead:
- Collection never raises; failures are reported as
{error_class, error_message}under the respective section key. - No sensitive data is sent (no job arguments, env vars, CLI args, or full SQL text in these control-plane metrics).
- CPU and network rates require two samples; on the first run you may see
nilforcpu_pctor network*_bytes_per_sfields until a second sample is available.
Configuration
You can enable or disable individual collectors and tune basic options via the standard DeadBro.configure block:
DeadBro.configure do |config|
# Enable the periodic job queue monitor (disabled by default)
config.job_queue_monitoring_enabled = true
# Enable best-effort collectors (all default to false)
config.enable_db_stats = true # ActiveRecord pool + ping latency
config.enable_process_stats = true # pid, hostname, RSS, GC, threads, fds
config.enable_system_stats = true # CPU%, memory, disk, network
# Filesystem paths to report disk usage for (default: ["/"])
config.disk_paths = ["/", "/var"]
# Network interfaces to ignore when computing rx/tx stats
config.interfaces_ignore = %w[lo docker0]
endExample Payload Shape
The control plane job sends a single JSON payload roughly shaped like:
{
"ts": "2025-01-01T12:00:00Z",
"app_name": "MyApp",
"env": "production",
"host": "app-1",
"pid": 12345,
"versions": {
"ruby": "3.1.2",
"rails": "7.1.0",
"sidekiq": "7.3.0"
},
"queue_system": "sidekiq",
"sidekiq": {
"processed": 1000,
"failed": 5,
"enqueued": 42,
"scheduled_size": 3,
"retry_size": 2,
"dead_size": 1,
"workers_size": 4,
"processes_size": 2,
"memory_rss_bytes": 123456789,
"queues": [
{"name": "default", "size": 10, "latency_s": 0.5}
]
},
"db": {
"available": true,
"pool": {
"size": 5,
"connections": 3,
"busy": 1,
"num_waiting": 0
},
"ping_ms": 2.1
},
"process": {
"pid": 12345,
"hostname": "app-1",
"uptime_s": 3600.5,
"rss_bytes": 123456789,
"thread_count": 20,
"fd_count": 128,
"gc": {
"heap_live_slots": 123_456,
"heap_free_slots": 12_345,
"total_allocated_objects": 1_234_567,
"major_gc_count": 10,
"minor_gc_count": 50
}
},
"system": {
"cpu_pct": 12.3,
"mem_used_bytes": 987654321,
"mem_total_bytes": 2147483648,
"mem_available_bytes": 1153433600,
"disk": {
"paths": [
{
"path": "/",
"disk_total_bytes": 107374182400,
"disk_free_bytes": 53687091200,
"disk_available_bytes": 53687091200
}
]
},
"net": {
"available": true,
"interfaces": [
{
"name": "eth0",
"rx_bytes": 123456,
"tx_bytes": 654321,
"rx_bytes_per_s": 1000.0,
"tx_bytes_per_s": 500.0
}
]
}
}
}Not all fields will be present in all environments; unsupported or unavailable metrics may be null or omitted, and any hard failures are captured in error_class / error_message fields per section.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/rubydevro/dead_bro.