# Gemfile
group :development do
gem 'rack-mini-profiler'
gem 'memory_profiler'
gem 'stackprof' # For flamegraphs
gem 'bullet' # N+1 detection
end
# config/initializers/rack_profiler.rb
if Rails.env.development?
require 'rack-mini-profiler'
# Memory profiling
Rack::MiniProfiler.config.enable_advanced_debugging_tools = true
# Store results in memory
Rack::MiniProfiler.config.storage = Rack::MiniProfiler::MemoryStore
# Position of profiler badge
Rack::MiniProfiler.config.position = 'bottom-right'
# Skip profiling for certain paths
Rack::MiniProfiler.config.skip_paths = ['/assets', '/packs']
end
# config/initializers/bullet.rb
if Rails.env.development?
Bullet.enable = true
Bullet.bullet_logger = true
Bullet.console = true
Bullet.add_footer = true
# Detect N+1 queries
Bullet.alert = true
# Detect unused eager loading
Bullet.unused_eager_loading_enable = true
# Detect counter cache misses
Bullet.counter_cache_enable = true
end
# Usage in code - manual profiling blocks
def index
Rack::MiniProfiler.step("Loading users") do
@users = User.includes(:posts).page(params[:page])
end
Rack::MiniProfiler.step("Loading categories") do
@categories = Category.all
end
end
# Memory profiling in console
require 'memory_profiler'
report = MemoryProfiler.report do
10_000.times { User.new(name: 'Test', email: 'test@example.com') }
end
report.pretty_print
# Flamegraph profiling
# Visit: http://localhost:3000/posts?pp=flamegraph
# Generates interactive flamegraph showing method call hierarchy
require 'benchmark'
# Basic benchmarking
Benchmark.bm do |x|
x.report("Using map:") { (1..1_000_000).map { |i| i * 2 } }
x.report("Using each:") {
result = []
(1..1_000_000).each { |i| result << i * 2 }
}
end
# Benchmark with labels and comparison
Benchmark.bmbm do |x|
x.report("find_by SQL:") {
1000.times { User.find_by(email: "test@example.com") }
}
x.report("where.first:") {
1000.times { User.where(email: "test@example.com").first }
}
x.report("find_by cached:") {
1000.times { User.find_by(email: "test@example.com") }
}
end
# Measuring memory allocation
def measure_memory(&block)
before = `ps -o rss= -p #{Process.pid}`.to_i
block.call
after = `ps -o rss= -p #{Process.pid}`.to_i
puts "Memory used: #{after - before} KB"
end
measure_memory do
1_000_000.times { "string #{rand}" }
end
# Rails benchmarking helper
class PostsController < ApplicationController
def index
result = benchmark("Loading posts with includes") do
Post.includes(:user, :comments).page(params[:page])
end
@posts = result
# Logs: "Loading posts with includes (23.4ms)"
end
end
# SQL query analysis
# Enable verbose query logs in development.rb
# config.active_record.verbose_query_logs = true
# This shows stacktrace for each SQL query in logs
Post.includes(:user).where(published: true).load
# Logs show exact file/line that triggered query
# Using Bullet to detect N+1 queries
# When enabled, Bullet will alert you:
# "USE: User => [:posts] for user in users loop"
# Profiling a specific method
class UserService
def self.create_users_with_profiling
Rack::MiniProfiler.step("Creating 1000 users") do
1000.times do |i|
User.create!(
name: "User #{i}",
email: "user#{i}@example.com"
)
end
end
end
end
# Database query profiling
ActiveRecord::Base.logger = Logger.new(STDOUT)
# Shows all SQL queries with timing
User.includes(:posts).where(active: true).to_a
# Explain queries
User.joins(:posts).where(posts: { published: true }).explain
# Shows PostgreSQL EXPLAIN ANALYZE output
rack-mini-profiler reveals performance bottlenecks in Rails apps. It displays database queries, rendering time, memory allocation on every page. I use Flamegraphs to visualize where time is spent. Memory profiling identifies allocation hotspots. Query analysis shows N+1 queries, slow queries. Profiling in development catches issues before production. ?pp=flamegraph generates interactive flame graphs. Database query backtraces show exactly which code triggered queries. rack-mini-profiler integrates with bullet for N+1 detection. Understanding profiling output guides optimization—focus on highest impact areas. Production profiling uses tools like New Relic, Skylight. Profiling transforms guessing into data-driven optimization.