pg_reports 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +143 -378
  4. data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
  5. data/app/views/layouts/pg_reports/application.html.erb +65 -8
  6. data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
  7. data/app/views/pg_reports/dashboard/_show_scripts.html.erb +55 -57
  8. data/app/views/pg_reports/dashboard/_show_styles.html.erb +18 -0
  9. data/app/views/pg_reports/dashboard/index.html.erb +109 -106
  10. data/app/views/pg_reports/dashboard/show.html.erb +26 -26
  11. data/config/locales/en.yml +488 -0
  12. data/config/locales/ru.yml +481 -0
  13. data/config/locales/uk.yml +481 -0
  14. data/lib/pg_reports/annotation_parser.rb +13 -1
  15. data/lib/pg_reports/compatibility.rb +3 -3
  16. data/lib/pg_reports/dashboard/reports_registry.rb +83 -12
  17. data/lib/pg_reports/definitions/schema_analysis/always_null_columns.yml +31 -0
  18. data/lib/pg_reports/definitions/schema_analysis/unused_columns.yml +32 -0
  19. data/lib/pg_reports/definitions/tables/unused_tables.yml +30 -0
  20. data/lib/pg_reports/definitions/tables/update_hotspots.yml +32 -0
  21. data/lib/pg_reports/module_generator.rb +2 -1
  22. data/lib/pg_reports/modules/schema_analysis.rb +261 -2
  23. data/lib/pg_reports/modules/system.rb +3 -3
  24. data/lib/pg_reports/query_monitor.rb +2 -6
  25. data/lib/pg_reports/report_definition.rb +20 -24
  26. data/lib/pg_reports/sql/schema_analysis/always_null_columns.sql +25 -0
  27. data/lib/pg_reports/sql/schema_analysis/unused_columns.sql +36 -0
  28. data/lib/pg_reports/sql/tables/unused_tables.sql +19 -0
  29. data/lib/pg_reports/sql/tables/update_hotspots.sql +26 -0
  30. data/lib/pg_reports/version.rb +1 -1
  31. metadata +9 -1
data/README.md CHANGED
@@ -29,267 +29,160 @@ A comprehensive PostgreSQL monitoring and analysis library for Rails application
29
29
 
30
30
  ## Installation
31
31
 
32
- Add to your Gemfile:
33
-
34
32
  ```ruby
33
+ # Gemfile
35
34
  gem "pg_reports"
36
-
37
- # Optional: for Telegram support
38
- gem "telegram-bot-ruby"
35
+ gem "telegram-bot-ruby" # optional, for Telegram delivery
39
36
  ```
40
37
 
41
- Run:
42
-
43
38
  ```bash
44
39
  bundle install
45
40
  ```
46
41
 
47
- ## Quick Start
48
-
49
- ### Mount the Dashboard
50
-
51
- Add to your `config/routes.rb`:
42
+ Mount the dashboard:
52
43
 
53
44
  ```ruby
45
+ # config/routes.rb
54
46
  Rails.application.routes.draw do
55
- # Mount in development only (recommended)
56
47
  if Rails.env.development?
57
48
  mount PgReports::Engine, at: "/pg_reports"
58
49
  end
59
50
 
60
- # Or with authentication
61
- authenticate :user, ->(u) { u.admin? } do
62
- mount PgReports::Engine, at: "/pg_reports"
63
- end
51
+ # Or with authentication:
52
+ # authenticate :user, ->(u) { u.admin? } do
53
+ # mount PgReports::Engine, at: "/pg_reports"
54
+ # end
64
55
  end
65
56
  ```
66
57
 
67
- Visit `http://localhost:3000/pg_reports` to access the dashboard.
58
+ Visit `http://localhost:3000/pg_reports`.
59
+
60
+ For query analysis, also enable `pg_stat_statements` — see [setup](#pg_stat_statements-setup) below.
68
61
 
69
- ### Use in Console or Code
62
+ ## Usage
70
63
 
71
64
  ```ruby
72
- # Get slow queries
65
+ # In console or code
73
66
  PgReports.slow_queries.display
67
+ PgReports.unused_indexes.each { |row| puts row["index_name"] }
74
68
 
75
- # Get unused indexes
76
- report = PgReports.unused_indexes
77
- report.each { |row| puts row["index_name"] }
78
-
79
- # Export to different formats
80
- report.to_text # Plain text
81
- report.to_csv # CSV
82
- report.to_a # Array of hashes
69
+ # Export
70
+ report = PgReports.expensive_queries
71
+ report.to_text
72
+ report.to_csv
73
+ report.to_a
83
74
 
84
- # Send to Telegram
85
- PgReports.expensive_queries.send_to_telegram
86
-
87
- # Health report
88
- PgReports.health_report.display
75
+ # Telegram
76
+ PgReports.slow_queries.send_to_telegram
89
77
  ```
90
78
 
91
- ## Configuration
79
+ **[Full list of reports →](docs/reports.md)**
92
80
 
93
- Create an initializer `config/initializers/pg_reports.rb`:
81
+ ## Configuration
94
82
 
95
83
  ```ruby
84
+ # config/initializers/pg_reports.rb
96
85
  PgReports.configure do |config|
97
86
  # Telegram (optional)
98
87
  config.telegram_bot_token = ENV["PG_REPORTS_TELEGRAM_TOKEN"]
99
- config.telegram_chat_id = ENV["PG_REPORTS_TELEGRAM_CHAT_ID"]
100
-
101
- # Query thresholds
102
- config.slow_query_threshold_ms = 100 # Queries slower than this
103
- config.heavy_query_threshold_calls = 1000 # Queries with more calls
104
- config.expensive_query_threshold_ms = 10000 # Total time threshold
88
+ config.telegram_chat_id = ENV["PG_REPORTS_TELEGRAM_CHAT_ID"]
105
89
 
106
- # Index thresholds
107
- config.unused_index_threshold_scans = 50 # Index with fewer scans
90
+ # Thresholds
91
+ config.slow_query_threshold_ms = 100
92
+ config.heavy_query_threshold_calls = 1000
93
+ config.expensive_query_threshold_ms = 10_000
94
+ config.unused_index_threshold_scans = 50
95
+ config.bloat_threshold_percent = 20
96
+ config.dead_rows_threshold = 10_000
108
97
 
109
- # Table thresholds
110
- config.bloat_threshold_percent = 20 # Tables with more bloat
111
- config.dead_rows_threshold = 10000 # Dead rows needing vacuum
98
+ # Output
99
+ config.max_query_length = 200
112
100
 
113
- # Output settings
114
- config.max_query_length = 200 # Truncate queries in text output
115
-
116
- # Dashboard authentication (optional)
117
- config.dashboard_auth = -> {
101
+ # Auth (optional)
102
+ config.dashboard_auth = -> {
118
103
  authenticate_or_request_with_http_basic do |user, pass|
119
- user == "admin" && pass == "secret"
104
+ user == ENV["PG_REPORTS_USER"] && pass == ENV["PG_REPORTS_PASSWORD"]
120
105
  end
121
106
  }
122
107
 
123
- # External fonts (Google Fonts)
124
- # Default: false (no external requests)
125
- config.load_external_fonts = ENV["PG_REPORTS_LOAD_EXTERNAL_FONTS"] == "true"
126
- # or simply:
127
- # config.load_external_fonts = true
128
-
108
+ # Google Fonts (default: false — no external requests)
109
+ config.load_external_fonts = false
129
110
  end
130
111
  ```
131
112
 
132
- ### Query Execution Security
133
-
134
- ⚠️ **Security Warning**: By default, the dashboard **does not allow** executing raw SQL queries via "Execute Query" and "EXPLAIN ANALYZE" buttons. This prevents accidental or malicious query execution in production environments.
135
-
136
- To enable query execution (only in secure environments):
113
+ <details>
114
+ <summary><strong>Locale (EN / RU / UK)</strong></summary>
137
115
 
138
- ```ruby
139
- PgReports.configure do |config|
140
- # Enable query execution from dashboard (default: false)
141
- config.allow_raw_query_execution = true
142
- end
143
- ```
116
+ PgReports follows your application's `I18n.locale`. Set it the way you set it for the rest of the app — there's no PgReports-specific knob. The dashboard supports `en`, `ru`, and `uk` out of the box.
144
117
 
145
- Or via environment variable:
118
+ </details>
146
119
 
147
- ```bash
148
- export PG_REPORTS_ALLOW_RAW_QUERY_EXECUTION=true
149
- ```
120
+ <details>
121
+ <summary><strong>Raw query execution (EXPLAIN ANALYZE / Execute Query)</strong></summary>
150
122
 
151
- **Recommended setup** (only enable in development/staging):
123
+ ⚠️ Disabled by default. The dashboard's "Execute Query" and "EXPLAIN ANALYZE" buttons require this opt-in.
152
124
 
153
125
  ```ruby
154
- # config/initializers/pg_reports.rb
155
126
  PgReports.configure do |config|
156
- # Only allow query execution in development/staging
157
127
  config.allow_raw_query_execution = Rails.env.development? || Rails.env.staging?
158
-
159
- # Combine with authentication for additional security
160
- config.dashboard_auth = -> {
161
- authenticate_or_request_with_http_basic do |user, pass|
162
- user == ENV["PG_REPORTS_USER"] && pass == ENV["PG_REPORTS_PASSWORD"]
163
- end
164
- }
165
128
  end
166
129
  ```
167
130
 
168
- When disabled:
169
- - API endpoints `/execute_query` and `/explain_analyze` return 403 Forbidden
170
- - UI buttons are disabled with explanation tooltips
171
- - Existing safety measures (SELECT/SHOW only, automatic LIMIT) still apply when enabled
131
+ </details>
172
132
 
173
- ## Query Source Tracking
133
+ <details>
134
+ <summary><strong>Query source tracking (Rails query logs)</strong></summary>
174
135
 
175
- PgReports automatically parses query annotations to show **where queries originated**. Works with:
136
+ PgReports parses query annotations to show **where queries originated**. On Rails 7.0+ use the built-in `ActiveRecord::QueryLogs` (no extra gem needed). On older Rails, install [Marginalia](https://github.com/basecamp/marginalia) — PgReports auto-detects both formats.
176
137
 
177
- ### Marginalia (recommended)
178
-
179
- If you use [marginalia](https://github.com/basecamp/marginalia), PgReports will automatically parse and display controller/action info in the **source** column.
138
+ Minimal setup — adds controller/action:
180
139
 
181
140
  ```ruby
182
- # Gemfile
183
- gem 'marginalia'
141
+ # config/application.rb
142
+ config.active_record.query_log_tags_enabled = true
143
+ config.active_record.query_log_tags = [:controller, :action]
184
144
  ```
185
145
 
186
- ### Rails 7+ Query Logs
146
+ To also surface **file path and line number** (so source links jump to the actual call site, not just the controller), add a custom `source_location` lambda that walks `caller_locations` and skips gem/framework frames:
187
147
 
188
148
  ```ruby
189
149
  # config/application.rb
190
150
  config.active_record.query_log_tags_enabled = true
191
- config.active_record.query_log_tags = [:controller, :action]
151
+ config.active_record.query_log_tags = [
152
+ :controller,
153
+ :action,
154
+ :job,
155
+ {
156
+ source_location: -> {
157
+ ignore = %r{/(gems|active_record|active_support|active_model|railties|
158
+ action_controller|action_view|action_pack|action_dispatch|
159
+ rack|core_ext|relation|associations|scoping|connection_adapters)/}x
160
+ loc = caller_locations.find { |l| !l.path.match?(ignore) }
161
+ "#{loc.path}:#{loc.lineno}" if loc
162
+ }
163
+ }
164
+ ]
192
165
  ```
193
166
 
194
- ## Available Reports
195
-
196
- ### Queries (requires pg_stat_statements)
197
-
198
- | Method | Description |
199
- |--------|-------------|
200
- | `slow_queries` | Queries with high mean execution time |
201
- | `heavy_queries` | Most frequently called queries |
202
- | `expensive_queries` | Queries consuming most total time |
203
- | `missing_index_queries` | Queries potentially missing indexes |
204
- | `low_cache_hit_queries` | Queries with poor cache utilization |
205
- | `temp_file_queries` | 🆕 Queries spilling to disk via temporary files |
206
- | `all_queries` | All query statistics |
207
- | `reset_statistics!` | Reset pg_stat_statements data |
208
-
209
- ### Indexes
210
-
211
- | Method | Description |
212
- |--------|-------------|
213
- | `unused_indexes` | Indexes rarely or never scanned |
214
- | `duplicate_indexes` | Redundant indexes |
215
- | `invalid_indexes` | Indexes that failed to build |
216
- | `missing_indexes` | Tables potentially missing indexes |
217
- | `inefficient_indexes` | 🆕 Indexes with high read-to-fetch ratio (misaligned column order) |
218
- | `fk_without_indexes` | 🆕 Foreign keys missing indexes on child table |
219
- | `index_correlation` | 🆕 Low physical correlation causing random I/O |
220
- | `index_usage` | Index scan statistics |
221
- | `bloated_indexes` | Indexes with high bloat |
222
- | `index_sizes` | Index disk usage |
223
-
224
- ### Tables
225
-
226
- | Method | Description |
227
- |--------|-------------|
228
- | `table_sizes` | Table disk usage |
229
- | `bloated_tables` | Tables with high dead tuple ratio |
230
- | `vacuum_needed` | Tables needing vacuum |
231
- | `row_counts` | Table row counts |
232
- | `cache_hit_ratios` | Table cache statistics |
233
- | `seq_scans` | Tables with high sequential scans |
234
- | `tables_without_pk` | 🆕 Tables missing primary keys |
235
- | `recently_modified` | Tables with recent activity |
236
-
237
- ### Connections
238
-
239
- | Method | Description |
240
- |--------|-------------|
241
- | `active_connections` | Current database connections |
242
- | `connection_stats` | Connection statistics by state |
243
- | `long_running_queries` | Queries running for extended period |
244
- | `blocking_queries` | Queries blocking others |
245
- | `locks` | Current database locks |
246
- | `idle_connections` | Idle connections |
247
- | `pool_usage` | Connection pool utilization analysis |
248
- | `pool_wait_times` | Resource wait time analysis |
249
- | `pool_saturation` | Pool health warnings with recommendations |
250
- | `connection_churn` | Connection lifecycle and churn rate analysis |
251
- | `kill_connection(pid)` | Terminate a backend process |
252
- | `cancel_query(pid)` | Cancel a running query |
253
-
254
- ### System
255
-
256
- | Method | Description |
257
- |--------|-------------|
258
- | `database_sizes` | Size of all databases |
259
- | `settings` | PostgreSQL configuration |
260
- | `extensions` | Installed extensions |
261
- | `activity_overview` | Current activity summary |
262
- | `wraparound_risk` | 🆕 Transaction ID wraparound proximity |
263
- | `checkpoint_stats` | 🆕 Checkpoint and bgwriter statistics (PG 12–18+) |
264
- | `cache_stats` | Database cache statistics |
265
- | `pg_stat_statements_available?` | Check if extension is ready |
266
- | `enable_pg_stat_statements!` | Create the extension |
267
-
268
- ## pg_stat_statements Setup
269
-
270
- For query analysis, you need to enable `pg_stat_statements`:
167
+ PgReports recognizes the `source_location` tag and splits it into file and line for the **source** column.
168
+
169
+ </details>
170
+
171
+ ## pg_stat_statements setup
271
172
 
272
173
  1. Edit `postgresql.conf`:
273
174
  ```
274
175
  shared_preload_libraries = 'pg_stat_statements'
275
176
  pg_stat_statements.track = all
276
177
  ```
178
+ 2. Restart PostgreSQL: `sudo systemctl restart postgresql`
179
+ 3. Create the extension (via dashboard button or `PgReports.enable_pg_stat_statements!`).
277
180
 
278
- 2. Restart PostgreSQL:
279
- ```bash
280
- sudo systemctl restart postgresql
281
- ```
282
-
283
- 3. Create extension (via dashboard or console):
284
- ```ruby
285
- PgReports.enable_pg_stat_statements!
286
- ```
181
+ > PgReports does **not** require the `pg_read_all_settings` role — extension availability is detected directly. Works with CloudnativePG, managed databases, and other restricted environments.
287
182
 
288
- > **Note**: PgReports does **not** require the `pg_read_all_settings` role. It detects `pg_stat_statements` availability by directly querying the extension, making it compatible with CloudnativePG, managed databases, and other environments with restricted permissions.
183
+ ## Report object
289
184
 
290
- ## Report Object
291
-
292
- Every method returns a `PgReports::Report` object:
185
+ Every method returns a `PgReports::Report`:
293
186
 
294
187
  ```ruby
295
188
  report = PgReports.slow_queries
@@ -319,247 +212,119 @@ report.map { |row| row["query"] }
319
212
  report.select { |row| row["calls"] > 100 }
320
213
  ```
321
214
 
322
- ## Web Dashboard
323
-
324
- The dashboard provides:
325
-
326
- - 📊 Overview of all report categories with descriptions
327
- - ⚡ One-click report execution
328
- - 🔍 Filter parameters - adjust thresholds and limits on the fly
329
- - 🔍 Expandable rows for full query text
330
- - 📋 Copy query to clipboard
331
- - 📥 Download in multiple formats (TXT, CSV, JSON)
332
- - 📨 Send to Telegram
333
- - 🔧 pg_stat_statements management
334
- - 🔄 Sortable columns - click headers to sort ascending/descending
335
- - 📌 Save records for comparison - track before/after optimization results
336
- - 📊 EXPLAIN ANALYZE - run query plans directly from the dashboard
337
- - 🗑️ Migration generator - create Rails migrations to drop unused indexes
338
- - 🔗 IDE integration - click source locations to open in your IDE
215
+ ## Dashboard features
339
216
 
340
- ### IDE Integration
217
+ The dashboard provides one-click execution, sortable columns, expandable rows, filter parameters, multi-format export, Telegram delivery, and pg_stat_statements management.
341
218
 
342
- Click on source locations in reports to open the file directly in your IDE. Supported IDEs:
219
+ <details>
220
+ <summary><strong>EXPLAIN ANALYZE — query plan analyzer</strong></summary>
343
221
 
344
- - **VS Code (WSL)** - for Windows Subsystem for Linux
345
- - **VS Code** - direct path for native Linux/macOS
346
- - **RubyMine**
347
- - **IntelliJ IDEA**
348
- - **Cursor (WSL)** - for Windows Subsystem for Linux
349
- - **Cursor**
222
+ Expand a row with a query, click **📊 EXPLAIN ANALYZE**. Shows:
350
223
 
351
- Use the ⚙️ button to set your default IDE and skip the selection menu.
224
+ - **Status indicator** (🟢🟡🔴) overall query health
225
+ - **Key metrics** — planning/execution time, cost, rows
226
+ - **Detected problems** — sequential scans on large tables, high-cost ops, sorts spilling to disk, slow sorts (>1s), inaccurate row estimates (>10× off), slow execution
227
+ - **Recommendations** for each issue
228
+ - **Color-coded plan** — node types tinted by performance impact (green: efficient, blue: normal, yellow: potential issue)
229
+ - **Line annotations** highlighting problems on specific plan lines
352
230
 
353
- ### Filter Parameters
231
+ Queries from `pg_stat_statements` with parameter placeholders (`$1`, `$2`) prompt for parameter values before analysis.
354
232
 
355
- Each report page includes a collapsible "Параметры фильтрации" (Filter Parameters) section where you can:
233
+ Requires `config.allow_raw_query_execution = true`.
356
234
 
357
- 1. **Adjust thresholds** - Override default thresholds (e.g., slow query threshold, unused index scans)
358
- 2. **Change limits** - Set the maximum number of results to display
359
- 3. **Real-time refresh** - Reports automatically refresh when you change parameters
235
+ </details>
360
236
 
361
- Parameters show their current configured values and allow you to experiment with different thresholds without changing your configuration file.
237
+ <details>
238
+ <summary><strong>SQL Query Monitor — real-time query capture</strong></summary>
362
239
 
363
- ### Save Records for Comparison
240
+ Live capture of all SQL executed by your Rails app. Click **▶ Start Monitoring**, run any operation, watch the queries appear with:
364
241
 
365
- When optimizing queries, you can save records to compare before/after results:
242
+ - SQL with syntax highlighting
243
+ - Duration (color-coded: 🟢 <10ms, 🟡 <100ms, 🔴 >100ms)
244
+ - Source location with click-to-IDE
245
+ - Timestamp
366
246
 
367
- 1. Expand a row and click "📌 Save for Comparison"
368
- 2. Saved records appear above the results table
369
- 3. Click saved records to expand and see all details
370
- 4. Clear all or remove individual saved records
371
-
372
- Records are stored in browser localStorage per report type.
373
-
374
- ### EXPLAIN ANALYZE
375
-
376
- The advanced query analyzer provides intelligent problem detection and recommendations:
377
-
378
- 1. Expand a row with a query
379
- 2. Click "📊 EXPLAIN ANALYZE"
380
- 3. View the color-coded execution plan with:
381
- - **🟢🟡🔴 Status indicator** - Overall query health assessment
382
- - **📈 Key metrics** - Planning/Execution time, Cost, Rows
383
- - **⚠️ Detected problems** - Sequential scans, high costs, slow sorts, estimation errors
384
- - **💡 Recommendations** - Actionable advice for each issue
385
- - **🎨 Colored plan** - Node types color-coded by performance impact:
386
- - 🟢 Green: Efficient operations (Index Scan, Hash Join)
387
- - 🔵 Blue: Normal operations (Bitmap Scan, HashAggregate)
388
- - 🟡 Yellow: Potential issues (Seq Scan, Sort, Materialize)
389
- - **Line-by-line annotations** - Problems highlighted on specific plan lines
390
-
391
- **Problem Detection:**
392
- - Sequential scans on large tables (> 1000 rows)
393
- - High-cost operations (> 10,000 cost units)
394
- - Sorts spilling to disk
395
- - Slow sort operations (> 1s)
396
- - Inaccurate row estimates (> 10x off)
397
- - Slow execution/planning times
398
-
399
- > Note: Queries with parameter placeholders ($1, $2) from pg_stat_statements require parameter input before analysis.
400
-
401
- ### SQL Query Monitoring
402
-
403
- Monitor all SQL queries executed in your Rails application in real-time:
404
-
405
- 1. Visit the dashboard at `/pg_reports`
406
- 2. Click **"▶ Start Monitoring"** button in the SQL Query Monitor panel
407
- 3. Execute operations in your application (web requests, console commands, background jobs)
408
- 4. View captured queries in the dashboard with:
409
- - **SQL text** - Formatted with syntax highlighting
410
- - **Execution duration** - Color-coded: 🟢 green (< 10ms), 🟡 yellow (< 100ms), 🔴 red (> 100ms)
411
- - **Source location** - File:line with click-to-open in IDE
412
- - **Timestamp** - When the query was executed
413
- 5. Click **"⏹ Stop Monitoring"** when done
414
-
415
- **Features:**
416
- - Uses Rails' built-in **ActiveSupport::Notifications** (`sql.active_record` events)
417
- - Global monitoring session (shared by all dashboard users)
418
- - Automatically filters internal queries (SCHEMA, CACHE, pg_reports' own queries)
419
- - Keeps last N queries in memory (configurable, default 100)
420
- - 2-second auto-refresh while monitoring is active
421
- - Session-based tracking with unique IDs
422
- - Logged to `log/pg_reports.log` in JSON Lines format
423
-
424
- **Configuration:**
247
+ Built on `ActiveSupport::Notifications` (`sql.active_record`). Filters internal queries (SCHEMA / CACHE / pg_reports' own). Logged to `log/pg_reports.log` (JSON Lines). Configurable buffer size and backtrace filter:
425
248
 
426
249
  ```ruby
427
250
  PgReports.configure do |config|
428
- # Query monitoring
429
251
  config.query_monitor_log_file = Rails.root.join("log", "custom_monitor.log")
430
- config.query_monitor_max_queries = 200 # Keep last 200 queries (default: 100)
431
-
432
- # Custom backtrace filtering to show only application code
433
- config.query_monitor_backtrace_filter = ->(location) {
434
- !location.path.match?(%r{/(gems|ruby|railties)/})
435
- }
252
+ config.query_monitor_max_queries = 200
253
+ config.query_monitor_backtrace_filter = ->(loc) { !loc.path.match?(%r{/(gems|ruby|railties)/}) }
436
254
  end
437
255
  ```
438
256
 
439
- **Log Format:**
257
+ Use cases: debugging N+1, identifying slow queries during feature development, tracking down unexpected queries, teaching ActiveRecord behavior.
440
258
 
441
- The log file uses JSON Lines format (one JSON object per line):
259
+ </details>
442
260
 
443
- ```json
444
- {"type":"session_start","session_id":"550e8400-e29b-41d4-a716-446655440000","timestamp":"2024-02-07T10:30:00Z"}
445
- {"type":"query","session_id":"550e8400-e29b-41d4-a716-446655440000","sql":"SELECT * FROM users WHERE id = 1","duration_ms":2.34,"name":"User Load","source_location":{"file":"app/controllers/users_controller.rb","line":15,"method":"show"},"timestamp":"2024-02-07T10:30:01Z"}
446
- {"type":"session_end","session_id":"550e8400-e29b-41d4-a716-446655440000","timestamp":"2024-02-07T10:35:00Z"}
447
- ```
448
-
449
- **Use Cases:**
450
- - 🐛 Debug N+1 query problems during development
451
- - 🐌 Identify slow queries in real-time
452
- - 🔍 Track down source of unexpected queries
453
- - 📊 Monitor query patterns during feature development
454
- - 📚 Teaching tool for understanding ActiveRecord behavior
455
-
456
- ### Migration Generator
457
-
458
- For unused or invalid indexes, generate Rails migrations:
459
-
460
- 1. Go to Indexes → Unused Indexes
461
- 2. Expand a row and click "🗑️ Generate Migration"
462
- 3. Copy the code or create the file directly
463
- 4. The file opens automatically in your configured IDE
464
-
465
- ### Connection Pool Analytics
261
+ <details>
262
+ <summary><strong>Connection pool analytics</strong></summary>
466
263
 
467
- Monitor your connection pool health with specialized reports:
264
+ Four specialized reports under the **Connections** category:
468
265
 
469
- **Pool Usage** - Real-time utilization metrics:
470
- - Total, active, idle connections per database
471
- - Pool utilization percentage with 🟢🟡🔴 indicators
472
- - Idle in transaction connections (resource waste)
473
- - Available connection capacity
474
-
475
- **Wait Times** - Identify resource bottlenecks:
476
- - Queries waiting for locks, I/O, or network
477
- - Wait event types (ClientRead, Lock, IO)
478
- - Wait duration with severity thresholds
479
- - Helps diagnose contention issues
480
-
481
- **Pool Saturation** - Health warnings with actionable recommendations:
482
- - Overall pool metrics with status indicators
483
- - Automatic severity assessment (Normal/Elevated/Warning/Critical)
484
- - Context-aware recommendations for each metric
485
- - Detects approaching exhaustion, high idle transactions
486
-
487
- **Connection Churn** - Lifecycle analysis:
488
- - Connection age distribution by application
489
- - Short-lived connection detection (< 10 seconds)
490
- - Churn rate percentage calculation
491
- - Identifies missing/misconfigured connection pooling
266
+ - **Pool Usage** total/active/idle per database, utilization %, idle-in-transaction count, available capacity
267
+ - **Wait Times** queries waiting on locks/IO/network with wait event types and severity
268
+ - **Pool Saturation** auto-classified (Normal / Elevated / Warning / Critical) with context-aware recommendations
269
+ - **Connection Churn** age distribution by application, short-lived (<10s) detection, churn-rate calculation, missing-pooling diagnosis
492
270
 
493
271
  ```ruby
494
- # Console usage
495
272
  PgReports.pool_usage.display
496
273
  PgReports.pool_saturation.display
497
274
  PgReports.connection_churn.display
498
275
  ```
499
276
 
500
- ### Authentication
277
+ </details>
501
278
 
502
- ```ruby
503
- PgReports.configure do |config|
504
- # HTTP Basic Auth
505
- config.dashboard_auth = -> {
506
- authenticate_or_request_with_http_basic do |user, pass|
507
- user == ENV["ADMIN_USER"] && pass == ENV["ADMIN_PASS"]
508
- end
509
- }
279
+ <details>
280
+ <summary><strong>IDE integration & migration generator</strong></summary>
510
281
 
511
- # Or use Devise
512
- config.dashboard_auth = -> {
513
- redirect_to main_app.root_path unless current_user&.admin?
514
- }
515
- end
516
- ```
282
+ Click any source location (file:line) in a report to open it in your IDE. Supported: VS Code, VS Code (WSL), RubyMine, IntelliJ IDEA, Cursor, Cursor (WSL). Use the ⚙️ button to set your default and skip the menu.
517
283
 
518
- ### External Fonts
284
+ For unused or invalid indexes, the dashboard generates a Rails migration: expand the row → **🗑️ Generate Migration** → copy the code or create the file directly (opens in your default IDE).
519
285
 
520
- By default, PgReports does **not** load external fonts.
286
+ </details>
521
287
 
522
- ```ruby
523
- PgReports.configure do |config|
524
- # Enable loading Google Fonts (optional)
525
- config.load_external_fonts = true
526
- end
527
- ```
288
+ <details>
289
+ <summary><strong>Save records for comparison</strong></summary>
290
+
291
+ When optimizing queries, click **📌 Save for Comparison** on any expanded row. Saved records persist in browser localStorage per report type and appear above the results table for before/after comparison.
528
292
 
529
- ## Telegram Integration
293
+ </details>
530
294
 
531
- 1. Create a bot via [@BotFather](https://t.me/BotFather)
532
- 2. Get your chat ID (add [@userinfobot](https://t.me/userinfobot) to get it)
533
- 3. Configure:
295
+ <details>
296
+ <summary><strong>AI prompt export</strong></summary>
297
+
298
+ The Export dropdown includes **Copy Prompt** (visible on actionable reports). It assembles a ready-to-paste prompt with problem description, fix instructions, and the actual report data — formatted for Claude Code, Cursor, Codex, or any code-aware AI assistant.
299
+
300
+ </details>
301
+
302
+ <details>
303
+ <summary><strong>Telegram delivery</strong></summary>
304
+
305
+ Get a bot token from [@BotFather](https://t.me/BotFather) and your chat ID from [@userinfobot](https://t.me/userinfobot), then:
534
306
 
535
307
  ```ruby
536
308
  PgReports.configure do |config|
537
309
  config.telegram_bot_token = "123456:ABC-DEF..."
538
- config.telegram_chat_id = "-1001234567890"
310
+ config.telegram_chat_id = "-1001234567890"
539
311
  end
540
- ```
541
-
542
- 4. Send reports:
543
312
 
544
- ```ruby
545
313
  PgReports.slow_queries.send_to_telegram
546
314
  PgReports.health_report.send_to_telegram_as_file
547
315
  ```
548
316
 
317
+ Reports under ~50 rows go as a message; larger ones are sent as a file attachment.
318
+
319
+ </details>
320
+
549
321
  ## Development
550
322
 
551
323
  ```bash
552
- # Clone the repo
553
324
  git clone https://github.com/yourusername/pg_reports
554
325
  cd pg_reports
555
-
556
- # Install dependencies
557
326
  bundle install
558
-
559
- # Run tests
560
327
  bundle exec rspec
561
-
562
- # Run linter
563
328
  bundle exec rubocop
564
329
  ```
565
330
 
@@ -567,14 +332,14 @@ bundle exec rubocop
567
332
 
568
333
  1. Fork it
569
334
  2. Create your feature branch (`git checkout -b feature/my-feature`)
570
- 3. Commit your changes (`git commit -am 'Add my feature'`)
571
- 4. Push to the branch (`git push origin feature/my-feature`)
335
+ 3. Commit your changes
336
+ 4. Push to the branch
572
337
  5. Create a Pull Request
573
338
 
574
339
  ## License
575
340
 
576
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
341
+ MIT. See [LICENSE.txt](LICENSE.txt).
577
342
 
578
343
  ## Acknowledgments
579
344
 
580
- Inspired by [rails-pg-extras](https://github.com/pawurb/rails-pg-extras) and built with ❤️ for the Rails community.
345
+ Inspired by [rails-pg-extras](https://github.com/pawurb/rails-pg-extras). UI built with [Claude](https://www.anthropic.com/claude) by Anthropic.