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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +143 -378
- data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
- data/app/views/layouts/pg_reports/application.html.erb +65 -8
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +55 -57
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +18 -0
- data/app/views/pg_reports/dashboard/index.html.erb +109 -106
- data/app/views/pg_reports/dashboard/show.html.erb +26 -26
- data/config/locales/en.yml +488 -0
- data/config/locales/ru.yml +481 -0
- data/config/locales/uk.yml +481 -0
- data/lib/pg_reports/annotation_parser.rb +13 -1
- data/lib/pg_reports/compatibility.rb +3 -3
- data/lib/pg_reports/dashboard/reports_registry.rb +83 -12
- data/lib/pg_reports/definitions/schema_analysis/always_null_columns.yml +31 -0
- data/lib/pg_reports/definitions/schema_analysis/unused_columns.yml +32 -0
- data/lib/pg_reports/definitions/tables/unused_tables.yml +30 -0
- data/lib/pg_reports/definitions/tables/update_hotspots.yml +32 -0
- data/lib/pg_reports/module_generator.rb +2 -1
- data/lib/pg_reports/modules/schema_analysis.rb +261 -2
- data/lib/pg_reports/modules/system.rb +3 -3
- data/lib/pg_reports/query_monitor.rb +2 -6
- data/lib/pg_reports/report_definition.rb +20 -24
- data/lib/pg_reports/sql/schema_analysis/always_null_columns.sql +25 -0
- data/lib/pg_reports/sql/schema_analysis/unused_columns.sql +36 -0
- data/lib/pg_reports/sql/tables/unused_tables.sql +19 -0
- data/lib/pg_reports/sql/tables/update_hotspots.sql +26 -0
- data/lib/pg_reports/version.rb +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
62
|
+
## Usage
|
|
70
63
|
|
|
71
64
|
```ruby
|
|
72
|
-
#
|
|
65
|
+
# In console or code
|
|
73
66
|
PgReports.slow_queries.display
|
|
67
|
+
PgReports.unused_indexes.each { |row| puts row["index_name"] }
|
|
74
68
|
|
|
75
|
-
#
|
|
76
|
-
report = PgReports.
|
|
77
|
-
report.
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
#
|
|
85
|
-
PgReports.
|
|
86
|
-
|
|
87
|
-
# Health report
|
|
88
|
-
PgReports.health_report.display
|
|
75
|
+
# Telegram
|
|
76
|
+
PgReports.slow_queries.send_to_telegram
|
|
89
77
|
```
|
|
90
78
|
|
|
91
|
-
|
|
79
|
+
**[Full list of reports →](docs/reports.md)**
|
|
92
80
|
|
|
93
|
-
|
|
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
|
|
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
|
-
#
|
|
107
|
-
config.
|
|
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
|
-
#
|
|
110
|
-
config.
|
|
111
|
-
config.dead_rows_threshold = 10000 # Dead rows needing vacuum
|
|
98
|
+
# Output
|
|
99
|
+
config.max_query_length = 200
|
|
112
100
|
|
|
113
|
-
#
|
|
114
|
-
config.
|
|
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 == "
|
|
104
|
+
user == ENV["PG_REPORTS_USER"] && pass == ENV["PG_REPORTS_PASSWORD"]
|
|
120
105
|
end
|
|
121
106
|
}
|
|
122
107
|
|
|
123
|
-
#
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
+
</details>
|
|
146
119
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
```
|
|
120
|
+
<details>
|
|
121
|
+
<summary><strong>Raw query execution (EXPLAIN ANALYZE / Execute Query)</strong></summary>
|
|
150
122
|
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
+
<details>
|
|
134
|
+
<summary><strong>Query source tracking (Rails query logs)</strong></summary>
|
|
174
135
|
|
|
175
|
-
PgReports
|
|
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
|
-
|
|
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
|
-
#
|
|
183
|
-
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
+
## Report object
|
|
289
184
|
|
|
290
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
219
|
+
<details>
|
|
220
|
+
<summary><strong>EXPLAIN ANALYZE — query plan analyzer</strong></summary>
|
|
343
221
|
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
+
Queries from `pg_stat_statements` with parameter placeholders (`$1`, `$2`) prompt for parameter values before analysis.
|
|
354
232
|
|
|
355
|
-
|
|
233
|
+
Requires `config.allow_raw_query_execution = true`.
|
|
356
234
|
|
|
357
|
-
|
|
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
|
-
|
|
237
|
+
<details>
|
|
238
|
+
<summary><strong>SQL Query Monitor — real-time query capture</strong></summary>
|
|
362
239
|
|
|
363
|
-
|
|
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
|
-
|
|
242
|
+
- SQL with syntax highlighting
|
|
243
|
+
- Duration (color-coded: 🟢 <10ms, 🟡 <100ms, 🔴 >100ms)
|
|
244
|
+
- Source location with click-to-IDE
|
|
245
|
+
- Timestamp
|
|
366
246
|
|
|
367
|
-
|
|
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
|
|
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
|
-
|
|
257
|
+
Use cases: debugging N+1, identifying slow queries during feature development, tracking down unexpected queries, teaching ActiveRecord behavior.
|
|
440
258
|
|
|
441
|
-
|
|
259
|
+
</details>
|
|
442
260
|
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
264
|
+
Four specialized reports under the **Connections** category:
|
|
468
265
|
|
|
469
|
-
**Pool Usage**
|
|
470
|
-
-
|
|
471
|
-
- Pool
|
|
472
|
-
-
|
|
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
|
-
|
|
277
|
+
</details>
|
|
501
278
|
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
+
</details>
|
|
521
287
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
293
|
+
</details>
|
|
530
294
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
|
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
|
|
571
|
-
4. Push to the branch
|
|
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
|
-
|
|
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)
|
|
345
|
+
Inspired by [rails-pg-extras](https://github.com/pawurb/rails-pg-extras). UI built with [Claude](https://www.anthropic.com/claude) by Anthropic.
|