Performance Profiling Stack

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

Overview

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

Development vs Production

Environment Tools Purpose
Development rack-mini-profiler, Prosopite, PgHero Interactive debugging, N+1 detection
Production AppSignal APM Request tracing, error tracking, host metrics

Components

1. Rack Mini Profiler

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

2. Prosopite (N+1 Query Detection)

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)

3. PgHero (PostgreSQL Dashboard)

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;

4. AppSignal APM (Production)

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

Quick Reference

Tool Environment Enable Access
rack-mini-profiler Development DEV_TRACE=true rails s Speed badge (top-left)
Flamegraphs Development Click badge → "flamegraph" In mini-profiler
Prosopite Development Automatic Check Rails logs for N+1 warnings
PgHero Dev/Staging Automatic /pghero (admin only)
memory_profiler Development bin/rails memory:expr EXPR='code' or manual stdout report (or pipe to file)
AppSignal Production/Staging Automatic https://appsignal.com/warmlyyours

When to Use Each Tool

Scenario Tool
Debug slow page in development rack-mini-profiler
Find N+1 queries while coding Prosopite (check Rails logs)
Analyze database performance PgHero
CPU profiling / hotspots Flamegraphs (via mini-profiler)
Memory leak investigation memory_profiler (bin/rails memory:expr or ?_profile_memory=1 on staging)
Per-gem startup cost bundle exec derailed bundle:mem
Production slow endpoints AppSignal APM
Production errors AppSignal Error Tracking
Post-deploy performance check AppSignal Deploy Markers

Memory Profiling Recipes

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

1. From a controller/action on staging

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

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

bin/rails memory:rake TASK=ahrefs:stats

4. Boot-time gem analysis

bundle exec derailed bundle:mem      # how much each gem costs to require
bundle exec derailed bundle:objects  # objects allocated per gem

Concrete patterns to grep for memory wins

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:

grep -rn "\.map(&:id)\|\.map(&:name)\|\.map(&:email)\|\.map(&:sku)" app/ lib/

Files

File Purpose
config/initializers/rack_mini_profiler.rb Mini profiler config
config/initializers/prosopite.rb N+1 detection config
config/initializers/pghero.rb PgHero controller setup
config/pghero.yml PgHero database config
config/appsignal.rb AppSignal APM config

Removed

  • scout_apm gem (replaced by this stack + AppSignal)
  • config/scout_apm.yml.obsolete

See Also