Skip to content

Commit 78e6f77

Browse files
committed
Added various profiling utilities
* Mini profiler for dev mode and for admins in production * PgHero for dev mode and for admins in production * Bullet for development * pg_stat_statements for both the dev and prod setups
1 parent 7935add commit 78e6f77

13 files changed

Lines changed: 104 additions & 1 deletion

File tree

Gemfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,13 @@ group :development do
7474
# Capture emails in-browser during development
7575
gem "letter_opener"
7676
gem "letter_opener_web"
77+
78+
# Detect N+1 queries and unused eager loads
79+
gem "bullet"
7780
end
81+
82+
# Per-request SQL timings and flamegraphs (enabled for dev + admin users in prod)
83+
gem "rack-mini-profiler"
84+
85+
# Mountable Postgres insights (uses pg_stat_statements)
86+
gem "pghero"

Gemfile.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ GEM
8686
brakeman (7.1.1)
8787
racc
8888
builder (3.3.0)
89+
bullet (8.1.0)
90+
activesupport (>= 3.0.0)
91+
uniform_notifier (~> 1.11)
8992
capybara (3.40.0)
9093
addressable
9194
matrix
@@ -264,6 +267,8 @@ GEM
264267
pg (1.6.2-aarch64-linux-musl)
265268
pg (1.6.2-x86_64-linux)
266269
pg (1.6.2-x86_64-linux-musl)
270+
pghero (3.7.0)
271+
activerecord (>= 7.1)
267272
pp (0.6.3)
268273
prettyprint
269274
prettyprint (0.2.0)
@@ -281,6 +286,8 @@ GEM
281286
raabro (1.4.0)
282287
racc (1.8.1)
283288
rack (3.2.4)
289+
rack-mini-profiler (4.0.1)
290+
rack (>= 1.2.0)
284291
rack-protection (4.2.1)
285292
base64 (>= 0.1.0)
286293
logger (>= 1.6.0)
@@ -436,6 +443,7 @@ GEM
436443
unicode-display_width (3.2.0)
437444
unicode-emoji (~> 4.1)
438445
unicode-emoji (4.1.0)
446+
uniform_notifier (1.18.0)
439447
uri (1.1.1)
440448
useragent (0.16.11)
441449
version_gem (1.1.9)
@@ -467,6 +475,7 @@ DEPENDENCIES
467475
bcrypt (~> 3.1)
468476
bootsnap
469477
brakeman
478+
bullet
470479
capybara
471480
debug
472481
factory_bot_rails
@@ -481,8 +490,10 @@ DEPENDENCIES
481490
omniauth-google-oauth2
482491
omniauth-rails_csrf_protection
483492
pg (~> 1.1)
493+
pghero
484494
propshaft
485495
puma (>= 5.0)
496+
rack-mini-profiler
486497
rails (~> 8.0.2)
487498
rspec-rails
488499
rubocop-rails-omakase

app/controllers/application_controller.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ class ApplicationController < ActionController::Base
22
allow_browser versions: :modern
33

44
rescue_from ActiveRecord::RecordNotFound, with: :render_404
5-
helper_method :current_user, :user_signed_in?
5+
helper_method :current_user, :user_signed_in?, :current_admin?
66
helper_method :activity_unread_count
7+
before_action :authorize_mini_profiler
78

89
private
910

@@ -21,10 +22,23 @@ def user_signed_in?
2122
current_user.present?
2223
end
2324

25+
def current_admin?
26+
current_user&.admin?
27+
end
28+
2429
def require_authentication
2530
redirect_to new_session_path, alert: 'Please sign in' unless user_signed_in?
2631
end
2732

33+
def authorize_mini_profiler
34+
return unless defined?(Rack::MiniProfiler)
35+
if Rails.env.development?
36+
Rack::MiniProfiler.authorize_request
37+
elsif Rails.env.production? && current_admin?
38+
Rack::MiniProfiler.authorize_request
39+
end
40+
end
41+
2842
def activity_unread_count
2943
return 0 unless current_user
3044
@activity_unread_count ||= Activity.where(user: current_user, hidden: false, read_at: nil).count

bin/docker-entrypoint

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ if [ -z "${LD_PRELOAD+x}" ]; then
66
export LD_PRELOAD
77
fi
88

9+
# Ensure gems are installed when using a persistent bundle volume.
10+
bundle check || bundle install
11+
912
# If running the rails server (directly or via bin/dev), create or migrate the database
1013
if { [ "$1" = "./bin/rails" ] && [ "$2" = "server" ]; } || [ "$1" = "./bin/dev" ]; then
1114
./bin/rails db:prepare

config/environments/development.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,22 @@
4949
# Append comments with runtime information tags to SQL queries in logs.
5050
config.active_record.query_log_tags_enabled = true
5151

52+
# Bullet: detect N+1 and eager loading issues.
53+
config.after_initialize do
54+
if defined?(Bullet)
55+
Bullet.enable = true
56+
Bullet.alert = false
57+
Bullet.bullet_logger = true
58+
Bullet.rails_logger = true
59+
Bullet.add_footer = false
60+
end
61+
end
62+
63+
# rack-mini-profiler in development.
64+
if defined?(Rack::MiniProfiler)
65+
Rack::MiniProfiler.config.position = :right
66+
end
67+
5268
# Highlight code that enqueued background job in logs.
5369
config.active_job.verbose_enqueue_logs = true
5470

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
Rack::MiniProfiler.config.position = "right"
4+
Rack::MiniProfiler.config.skip_paths ||= []
5+
Rack::MiniProfiler.config.skip_paths += ["/up"]
6+
Rack::MiniProfiler.config.enable_hotwire_turbo_drive_support = true
7+
8+
if Rails.env.development?
9+
Rack::MiniProfiler.config.authorization_mode = :allow_all
10+
else
11+
# Only allow signed-in admins in production; authorization happens later in ApplicationController.
12+
Rack::MiniProfiler.config.authorization_mode = :allow_authorized
13+
end

config/routes.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353

5454
post "messages/:id/read", to: "messages#read", as: :read_message
5555

56+
if defined?(PgHero)
57+
constraints AdminConstraint.new do
58+
mount PgHero::Engine, at: "/pghero"
59+
end
60+
end
61+
5662
if Rails.env.development?
5763
mount LetterOpenerWeb::Engine, at: "/letter_opener"
5864
end

deploy/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ This is a minimal, single-host setup for running Hackorum on a VPS (e.g., Hetzne
4444
5) Verify:
4545
- Browse to your domain; or `curl -f http://localhost:3000/up` from the host (`docker compose exec web ...` inside the network).
4646

47+
## Observability
48+
- Query stats: pg_stat_statements is preloaded via the Postgres config and created on first init via `/docker-entrypoint-initdb.d/01_pg_stat_statements.sql`. For existing databases, run `CREATE EXTENSION IF NOT EXISTS pg_stat_statements;` once. PgHero is available at `/pghero` for signed-in admin users.
49+
- Request-level profiling: rack-mini-profiler is available; in production it renders only for signed-in admin users.
50+
4751
## Environment variables (deploy/.env)
4852
- `SECRET_KEY_BASE` (required)
4953
- `DATABASE_URL` (defaults to local Postgres via env interpolation)

deploy/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ services:
1414
- pgwal:/var/lib/postgresql/wal-archive
1515
- pgbackups:/backups
1616
- ./postgres/postgresql.conf:/etc/postgresql/postgresql.conf:ro # copy from postgresql.conf.example
17+
- ./postgres/init:/docker-entrypoint-initdb.d:ro
1718
- ./postgres/entrypoint.sh:/usr/local/bin/custom-entrypoint.sh:ro
1819
command:
1920
[
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Ensure pg_stat_statements is available in the default database.
2+
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

0 commit comments

Comments
 (0)