Skip to content

Performance Profiling Stack

Date: November 26, 2025 (Updated: November 30, 2025)
Category: Development Tools
Status: Active

A modern, open-source performance debugging stack for Rails development and local analysis. These tools complement AppSignal which provides production APM.

EnvironmentToolsPurpose
Developmentrack-mini-profiler, Prosopite, PgHeroInteractive debugging, N+1 detection
ProductionAppSignal APMRequest tracing, error tracking, host metrics

Purpose: Visual performance dashboard showing request timing breakdown

# Gemfile (development group)
gem 'rack-mini-profiler'
gem 'stackprof' # Enables flamegraphs
gem 'memory_profiler' # Memory leak detection
gem 'flamegraph' # Flamegraph visualization

Usage:

  • Enable with DEV_TRACE=true environment variable
  • Speed badge appears in top-left corner of pages
  • Click badge for detailed breakdown (SQL, Ruby, Views)
  • Click “flamegraph” for CPU profiling visualization

Configuration: config/initializers/rack_mini_profiler.rb

Rack::MiniProfiler.config.enabled = ENV.fetch('DEV_TRACE', false).to_b
Rack::MiniProfiler.config.position = 'top-left'
Rack::MiniProfiler.config.enable_advanced_debugging_tools = true if defined?(StackProf)

Excluded Paths:

  • /assets, /packs, /javascripts/webpack
  • /sidekiq, /pghero, /letter_opener
  • /v1, /api
  • /cable, /media

Purpose: Detect N+1 queries with zero false positives

Configuration: config/initializers/prosopite.rb

Prosopite.prosopite_logger = Rails.logger
Prosopite.ignore_queries = [
/sidekiq/i, /pghero/i, /devise/i, /paper_trail/i,
%r{app/workers/}, %r{app/mailers/}, %r{lib/tasks/}
# ... more patterns
]

How it works:

  • Monitors transaction call stacks (more accurate than Bullet)
  • Logs warnings to Rails logger in development
  • Can raise exceptions in test environment (disabled by default)

Purpose: PostgreSQL performance analysis and index recommendations

Access: https://crm.warmlyyours.me:3000/pghero (admin login required)

Features:

  • Slow query detection
  • Index suggestions (with hypopg for what-if analysis)
  • Connection monitoring
  • Vacuum health
  • Space usage analysis
  • EXPLAIN ANALYZE for queries

Configuration: config/pghero.yml

Required PostgreSQL Extensions:

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS hypopg;

Purpose: Production performance monitoring, error tracking, and host metrics

Access: https://appsignal.com/warmlyyours

Features:

  • Request performance tracking with timing breakdown
  • Slow endpoint detection
  • Error tracking with stack traces
  • Host metrics (CPU, memory, disk)
  • Deploy markers for correlation
  • Anomaly detection and alerting

Configuration: config/appsignal.rb

Documentation: Infrastructure/MONITORING.md

ToolEnvironmentEnableAccess
rack-mini-profilerDevelopmentDEV_TRACE=true rails sSpeed badge (top-left)
FlamegraphsDevelopmentClick badge → “flamegraph”In mini-profiler
ProsopiteDevelopmentAutomaticCheck Rails logs for N+1 warnings
PgHeroDev/StagingAutomatic/pghero (admin only)
memory_profilerDevelopmentbin/rails memory:expr EXPR='code' or manualstdout report (or pipe to file)
AppSignalProduction/StagingAutomatichttps://appsignal.com/warmlyyours
ScenarioTool
Debug slow page in developmentrack-mini-profiler
Find N+1 queries while codingProsopite (check Rails logs)
Analyze database performancePgHero
CPU profiling / hotspotsFlamegraphs (via mini-profiler)
Memory leak investigationmemory_profiler (bin/rails memory:expr or ?_profile_memory=1 on staging)
Per-gem startup costbundle exec derailed bundle:mem
Production slow endpointsAppSignal APM
Production errorsAppSignal Error Tracking
Post-deploy performance checkAppSignal Deploy Markers

The memory_profiler gem ships in dev/test bundles. Three entry points:

Append ?_profile_memory=1 to any URL. The RequestMemoryTracer middleware (config/initializers/request_memory_tracer.rb) wraps the request in MemoryProfiler.report and writes the report inside the running Kamal app container to /var/www/heatwave/shared/log/memory_profiler/<timestamp>-<path>.txt (CRASH_LOG_DIR is still hardcoded to that legacy Capistrano-era path in the initializer; under Kamal/Docker it’s a plain container-internal directory, not a host bind-mount). Pull a report off the staging host with kamal app exec --reuse "cat /var/www/heatwave/shared/log/memory_profiler/<file>" or docker cp <container>:/var/www/heatwave/shared/log/memory_profiler/. .. Rate-limited to one capture per 60s per worker so you can’t accidentally DoS the staging servers.

2. From the command line — arbitrary expression

Section titled “2. From the command line — arbitrary expression”
Terminal window
bin/rails memory:expr EXPR='Order.pending.includes(:customer).to_a'
bin/rails memory:expr EXPR='Search.find(123).search_results.to_a' MAX=25

3. From the command line — wrap another rake task

Section titled “3. From the command line — wrap another rake task”
Terminal window
bin/rails memory:rake TASK=ahrefs:stats
Terminal window
bundle exec derailed bundle:mem # how much each gem costs to require
bundle exec derailed bundle:objects # objects allocated per gem

When auditing a hot path that allocates too much:

  • Model.where(...).map(&:id)Model.where(...).pluck(:id) (≈90% allocation reduction per the pawelurbanek.com benchmark).
  • Model.where(...).map(&:single_column)pluck(:single_column).
  • Model.where(...).map { |r| [r.id, r.name] }pluck(:id, :name).
  • Need a few model methods but not full records? .select(:id, :col_a, :col_b) returns lightweight model instances (~65% reduction); accessing unfetched columns raises ActiveModel::MissingAttributeError so this catches over-fetching at runtime.
  • Model.all.eachfind_each (or in_batches) for any iteration over more than a few hundred records.
  • Loading full records just to call .count / .exists? → use those query methods directly.

To find candidates in this repo:

Terminal window
grep -rn "\.map(&:id)\|\.map(&:name)\|\.map(&:email)\|\.map(&:sku)" app/ lib/
FilePurpose
config/initializers/rack_mini_profiler.rbMini profiler config
config/initializers/prosopite.rbN+1 detection config
config/initializers/pghero.rbPgHero controller setup
config/pghero.ymlPgHero database config
config/appsignal.rbAppSignal APM config
  • scout_apm gem (replaced by this stack + AppSignal)
  • config/scout_apm.yml.obsolete