rails-pg-extras 3.2.6 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +52 -42
- data/app/controllers/rails_pg_extras/web/actions_controller.rb +26 -0
- data/app/controllers/rails_pg_extras/web/application_controller.rb +20 -0
- data/app/controllers/rails_pg_extras/web/queries_controller.rb +44 -0
- data/app/views/layouts/rails_pg_extras/web/application.html.erb +26 -0
- data/app/views/rails_pg_extras/web/queries/_diagnose.html.erb +9 -0
- data/app/views/rails_pg_extras/web/queries/_result.html.erb +21 -0
- data/app/views/rails_pg_extras/web/queries/_unavailable_extensions_warning.html.erb +13 -0
- data/app/views/rails_pg_extras/web/queries/index.html.erb +21 -0
- data/app/views/rails_pg_extras/web/queries/show.html.erb +24 -0
- data/app/views/rails_pg_extras/web/shared/_queries_selector.html.erb +11 -0
- data/config/routes.rb +9 -0
- data/lib/rails_pg_extras/diagnose_data.rb +12 -0
- data/lib/{rails-pg-extras → rails_pg_extras}/diagnose_print.rb +2 -2
- data/lib/rails_pg_extras/index_info.rb +11 -0
- data/lib/rails_pg_extras/index_info_print.rb +6 -0
- data/lib/rails_pg_extras/railtie.rb +7 -0
- data/lib/rails_pg_extras/table_info.rb +11 -0
- data/lib/rails_pg_extras/table_info_print.rb +6 -0
- data/lib/{rails-pg-extras → rails_pg_extras}/tasks/all.rake +7 -7
- data/lib/rails_pg_extras/version.rb +5 -0
- data/lib/rails_pg_extras/web/engine.rb +7 -0
- data/lib/rails_pg_extras/web.rb +6 -0
- data/lib/{rails-pg-extras.rb → rails_pg_extras.rb} +24 -23
- data/rails-pg-extras.gemspec +21 -17
- data/spec/smoke_spec.rb +7 -6
- data/spec/spec_helper.rb +4 -4
- metadata +30 -16
- data/lib/rails-pg-extras/diagnose_data.rb +0 -12
- data/lib/rails-pg-extras/index_info.rb +0 -11
- data/lib/rails-pg-extras/index_info_print.rb +0 -6
- data/lib/rails-pg-extras/railtie.rb +0 -7
- data/lib/rails-pg-extras/table_info.rb +0 -11
- data/lib/rails-pg-extras/table_info_print.rb +0 -6
- data/lib/rails-pg-extras/version.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a182232adb71ba714ab127505aa21e410291536a6f6992fed863e6beba4d11d6
|
4
|
+
data.tar.gz: c219ee40b92eed9f873dc7a6fbe8e0cd80dd13e70cdd7048827b639d9dbd2f6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82e1cd92bf47fb1588d49bb59fbf10ad5cd24c5878b5fec4025db301707936e6196f8af2ed27d384a5259349be5a6dbfdfc678b1e08ad029a37cc0de178d96ba
|
7
|
+
data.tar.gz: 2d7189e0a3953323e5872ad817f015d3f023c90832fe9cf76d42485956f2868089bc9ae00532c377d54dc7f1f764832093cf19778d0f3be02f053eb9d19b9ad1
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ You can read this blog post for detailed step by step tutorial on how to [optimi
|
|
8
8
|
|
9
9
|
**Shameless plug:** rails-pg-extras is one of the tools that I use when conducting Rails performance audits. [Check out my offer](https://pawelurbanek.com/#rails-performance-tuning) if you need help with fine-tuning your app.
|
10
10
|
|
11
|
-
|
11
|
+
Optionally you can enable a visual interface:
|
12
12
|
|
13
13
|
![Web interface](https://github.com/pawurb/rails-pg-extras/raw/master/rails-pg-extras-web.png)
|
14
14
|
|
@@ -29,7 +29,7 @@ Alternative versions:
|
|
29
29
|
In your Gemfile
|
30
30
|
|
31
31
|
```ruby
|
32
|
-
gem "rails-pg-extras"
|
32
|
+
gem "rails-pg-extras", require: "rails_pg_extras"
|
33
33
|
```
|
34
34
|
|
35
35
|
`calls` and `outliers` queries require [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) extension.
|
@@ -37,7 +37,7 @@ gem "rails-pg-extras"
|
|
37
37
|
You can check if it is enabled in your database by running:
|
38
38
|
|
39
39
|
```ruby
|
40
|
-
|
40
|
+
RailsPgExtras.extensions
|
41
41
|
```
|
42
42
|
You should see the similar line in the output:
|
43
43
|
|
@@ -48,7 +48,7 @@ You should see the similar line in the output:
|
|
48
48
|
`ssl_used` requires `sslinfo` extension, and `buffercache_usage`/`buffercache_usage` queries need `pg_buffercache`. You can enable them all by running:
|
49
49
|
|
50
50
|
```ruby
|
51
|
-
|
51
|
+
RailsPgExtras.add_extensions
|
52
52
|
```
|
53
53
|
|
54
54
|
## Usage
|
@@ -60,7 +60,7 @@ rake pg_extras:cache_hit
|
|
60
60
|
```
|
61
61
|
|
62
62
|
```ruby
|
63
|
-
|
63
|
+
RailsPgExtras.cache_hit
|
64
64
|
```
|
65
65
|
```bash
|
66
66
|
+----------------+------------------------+
|
@@ -77,15 +77,15 @@ RailsPGExtras.cache_hit
|
|
77
77
|
By default the ASCII table is displayed, to change to format you need to specify the `in_format` parameter (`[:display_table, :hash, :array, :raw]` options are available):
|
78
78
|
|
79
79
|
```ruby
|
80
|
-
|
80
|
+
RailsPgExtras.cache_hit(in_format: :hash) =>
|
81
81
|
|
82
82
|
[{"name"=>"index hit rate", "ratio"=>"0.97796610169491525424"}, {"name"=>"table hit rate", "ratio"=>"0.96724294813466787989"}]
|
83
83
|
|
84
|
-
|
84
|
+
RailsPgExtras.cache_hit(in_format: :array) =>
|
85
85
|
|
86
86
|
[["index hit rate", "0.97796610169491525424"], ["table hit rate", "0.96724294813466787989"]]
|
87
87
|
|
88
|
-
|
88
|
+
RailsPgExtras.cache_hit(in_format: :raw) =>
|
89
89
|
|
90
90
|
#<PG::Result:0x00007f75777f7328 status=PGRES_TUPLES_OK ntuples=2 nfields=2 cmd_tuples=2>
|
91
91
|
```
|
@@ -93,7 +93,7 @@ RailsPGExtras.cache_hit(in_format: :raw) =>
|
|
93
93
|
Some methods accept an optional `args` param allowing you to customize queries:
|
94
94
|
|
95
95
|
```ruby
|
96
|
-
|
96
|
+
RailsPgExtras.long_running_queries(args: { threshold: "200 milliseconds" })
|
97
97
|
|
98
98
|
```
|
99
99
|
|
@@ -102,7 +102,7 @@ RailsPGExtras.long_running_queries(args: { threshold: "200 milliseconds" })
|
|
102
102
|
The simplest way to start using pg-extras is to execute a `diagnose` method. It runs a set of checks and prints out a report highlighting areas that may require additional investigation:
|
103
103
|
|
104
104
|
```ruby
|
105
|
-
|
105
|
+
RailsPgExtras.diagnose
|
106
106
|
|
107
107
|
$ rake pg_extras:diagnose
|
108
108
|
```
|
@@ -111,6 +111,16 @@ $ rake pg_extras:diagnose
|
|
111
111
|
|
112
112
|
Keep reading to learn about methods that `diagnose` uses under the hood.
|
113
113
|
|
114
|
+
## Visual interface
|
115
|
+
|
116
|
+
You can enable UI using a Rails engine by adding the following code in `config/routes.rb`:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
mount RailsPgExtras::Web::Engine, at: 'pg_extras'
|
120
|
+
```
|
121
|
+
|
122
|
+
On production environment you can enable HTTP basic auth by specifying `RAILS_PG_EXTRAS_USER` and `RAILS_PG_EXTRAS_PASSWORD` variables.
|
123
|
+
|
114
124
|
## Available methods
|
115
125
|
|
116
126
|
### `table_info`
|
@@ -118,7 +128,7 @@ Keep reading to learn about methods that `diagnose` uses under the hood.
|
|
118
128
|
This method displays metadata metrics for all or a selected table. You can use it to check the table's size, its cache hit metrics, and whether it is correctly indexed. Many sequential scans or no index scans are potential indicators of misconfigured indexes. This method aggregates data provided by other methods in an easy to analyze summary format.
|
119
129
|
|
120
130
|
```ruby
|
121
|
-
|
131
|
+
RailsPgExtras.table_info(args: { table_name: "users" })
|
122
132
|
|
123
133
|
| Table name | Table size | Table cache hit | Indexes cache hit | Estimated rows | Sequential scans | Indexes scans |
|
124
134
|
+------------+------------+-------------------+--------------------+----------------+------------------+---------------+
|
@@ -132,7 +142,7 @@ This method returns summary info about database indexes. You can check index siz
|
|
132
142
|
|
133
143
|
```ruby
|
134
144
|
|
135
|
-
|
145
|
+
RailsPgExtras.index_info(args: { table_name: "users" })
|
136
146
|
|
137
147
|
| Index name | Table name | Columns | Index size | Index scans | Null frac |
|
138
148
|
+-------------------------------+------------+----------------+------------+-------------+-----------+
|
@@ -148,7 +158,7 @@ RailsPGExtras.index_info(args: { table_name: "users" })
|
|
148
158
|
### `cache_hit`
|
149
159
|
|
150
160
|
```ruby
|
151
|
-
|
161
|
+
RailsPgExtras.cache_hit
|
152
162
|
|
153
163
|
$ rake pg_extras:cache_hit
|
154
164
|
|
@@ -167,7 +177,7 @@ This command provides information on the efficiency of the buffer cache, for bot
|
|
167
177
|
|
168
178
|
```ruby
|
169
179
|
|
170
|
-
|
180
|
+
RailsPgExtras.index_cache_hit
|
171
181
|
|
172
182
|
$ rake pg_extras:index_cache_hit
|
173
183
|
|
@@ -187,7 +197,7 @@ The same as `cache_hit` with each table's indexes cache hit info displayed separ
|
|
187
197
|
|
188
198
|
```ruby
|
189
199
|
|
190
|
-
|
200
|
+
RailsPgExtras.table_cache_hit
|
191
201
|
|
192
202
|
$ rake pg_extras:table_cache_hit
|
193
203
|
|
@@ -207,7 +217,7 @@ The same as `cache_hit` with each table's cache hit info displayed seperately.
|
|
207
217
|
|
208
218
|
```ruby
|
209
219
|
|
210
|
-
|
220
|
+
RailsPgExtras.db_settings
|
211
221
|
|
212
222
|
$ rake pg_extras:db_settings
|
213
223
|
|
@@ -229,7 +239,7 @@ This method displays values for selected PostgreSQL settings. You can compare th
|
|
229
239
|
|
230
240
|
```ruby
|
231
241
|
|
232
|
-
|
242
|
+
RailsPgExtras.ssl_used
|
233
243
|
|
234
244
|
| ssl_is_used |
|
235
245
|
+---------------------------------+
|
@@ -242,7 +252,7 @@ Returns boolean indicating if an encrypted SSL is currently used. Connecting to
|
|
242
252
|
### `index_usage`
|
243
253
|
|
244
254
|
```ruby
|
245
|
-
|
255
|
+
RailsPgExtras.index_usage
|
246
256
|
|
247
257
|
$ rake pg_extras:index_usage
|
248
258
|
|
@@ -261,7 +271,7 @@ This command provides information on the efficiency of indexes, represented as w
|
|
261
271
|
### `locks`
|
262
272
|
|
263
273
|
```ruby
|
264
|
-
|
274
|
+
RailsPgExtras.locks
|
265
275
|
|
266
276
|
$ rake pg_extras:locks
|
267
277
|
|
@@ -283,7 +293,7 @@ This command displays queries that have taken out an exclusive lock on a relatio
|
|
283
293
|
### `all_locks`
|
284
294
|
|
285
295
|
```ruby
|
286
|
-
|
296
|
+
RailsPgExtras.all_locks
|
287
297
|
|
288
298
|
$ rake pg_extras:all_locks
|
289
299
|
```
|
@@ -293,7 +303,7 @@ This command displays all the current locks, regardless of their type.
|
|
293
303
|
### `outliers`
|
294
304
|
|
295
305
|
```ruby
|
296
|
-
|
306
|
+
RailsPgExtras.outliers(args: { limit: 20 })
|
297
307
|
|
298
308
|
$ rake pg_extras:outliers
|
299
309
|
|
@@ -317,7 +327,7 @@ Typically, an efficient query will have an appropriate ratio of calls to total e
|
|
317
327
|
### `calls`
|
318
328
|
|
319
329
|
```ruby
|
320
|
-
|
330
|
+
RailsPgExtras.calls(args: { limit: 10 })
|
321
331
|
|
322
332
|
$ rake pg_extras:calls
|
323
333
|
|
@@ -338,7 +348,7 @@ This command is much like `pg:outliers`, but ordered by the number of times a st
|
|
338
348
|
### `blocking`
|
339
349
|
|
340
350
|
```ruby
|
341
|
-
|
351
|
+
RailsPgExtras.blocking
|
342
352
|
|
343
353
|
$ rake pg_extras:blocking
|
344
354
|
|
@@ -355,7 +365,7 @@ This command displays statements that are currently holding locks that other sta
|
|
355
365
|
### `total_index_size`
|
356
366
|
|
357
367
|
```ruby
|
358
|
-
|
368
|
+
RailsPgExtras.total_index_size
|
359
369
|
|
360
370
|
$ rake pg_extras:total_index_size
|
361
371
|
|
@@ -370,7 +380,7 @@ This command displays the total size of all indexes on the database, in MB. It i
|
|
370
380
|
### `index_size`
|
371
381
|
|
372
382
|
```ruby
|
373
|
-
|
383
|
+
RailsPgExtras.index_size
|
374
384
|
|
375
385
|
$ rake pg_extras:index_size
|
376
386
|
name | size
|
@@ -393,7 +403,7 @@ This command displays the size of each each index in the database, in MB. It is
|
|
393
403
|
### `table_size`
|
394
404
|
|
395
405
|
```ruby
|
396
|
-
|
406
|
+
RailsPgExtras.table_size
|
397
407
|
|
398
408
|
$ rake pg_extras:table_size
|
399
409
|
|
@@ -412,7 +422,7 @@ This command displays the size of each table and materialized view in the databa
|
|
412
422
|
### `table_indexes_size`
|
413
423
|
|
414
424
|
```ruby
|
415
|
-
|
425
|
+
RailsPgExtras.table_indexes_size
|
416
426
|
|
417
427
|
$ rake pg_extras:table_indexes_size
|
418
428
|
|
@@ -431,7 +441,7 @@ This command displays the total size of indexes for each table and materialized
|
|
431
441
|
### `total_table_size`
|
432
442
|
|
433
443
|
```ruby
|
434
|
-
|
444
|
+
RailsPgExtras.total_table_size
|
435
445
|
|
436
446
|
$ rake pg_extras:total_table_size
|
437
447
|
|
@@ -450,7 +460,7 @@ This command displays the total size of each table and materialized view in the
|
|
450
460
|
### `unused_indexes`
|
451
461
|
|
452
462
|
```ruby
|
453
|
-
|
463
|
+
RailsPgExtras.unused_indexes(args: { max_scans: 20 })
|
454
464
|
|
455
465
|
$ rake pg_extras:unused_indexes
|
456
466
|
|
@@ -470,7 +480,7 @@ This command displays indexes that have < 50 scans recorded against them, and ar
|
|
470
480
|
|
471
481
|
```ruby
|
472
482
|
|
473
|
-
|
483
|
+
RailsPgExtras.duplicate_indexes
|
474
484
|
|
475
485
|
| size | idx1 | idx2 | idx3 | idx4 |
|
476
486
|
+------------+--------------+----------------+----------+-----------+
|
@@ -483,7 +493,7 @@ This command displays multiple indexes that have the same set of columns, same o
|
|
483
493
|
|
484
494
|
```ruby
|
485
495
|
|
486
|
-
|
496
|
+
RailsPgExtras.null_indexes(args: { min_relation_size_mb: 10 })
|
487
497
|
|
488
498
|
$ rake pg_extras:null_indexes
|
489
499
|
|
@@ -502,7 +512,7 @@ This command displays indexes that contain `NULL` values. A high ratio of `NULL`
|
|
502
512
|
### `seq_scans`
|
503
513
|
|
504
514
|
```ruby
|
505
|
-
|
515
|
+
RailsPgExtras.seq_scans
|
506
516
|
|
507
517
|
$ rake pg_extras:seq_scans
|
508
518
|
|
@@ -526,7 +536,7 @@ This command displays the number of sequential scans recorded against all tables
|
|
526
536
|
### `long_running_queries`
|
527
537
|
|
528
538
|
```ruby
|
529
|
-
|
539
|
+
RailsPgExtras.long_running_queries(args: { threshold: "200 milliseconds" })
|
530
540
|
|
531
541
|
$ rake pg_extras:long_running_queries
|
532
542
|
|
@@ -543,7 +553,7 @@ This command displays currently running queries, that have been running for long
|
|
543
553
|
### `records_rank`
|
544
554
|
|
545
555
|
```ruby
|
546
|
-
|
556
|
+
RailsPgExtras.records_rank
|
547
557
|
|
548
558
|
$ rake pg_extras:records_rank
|
549
559
|
|
@@ -563,7 +573,7 @@ This command displays an estimated count of rows per table, descending by estima
|
|
563
573
|
### `bloat`
|
564
574
|
|
565
575
|
```ruby
|
566
|
-
|
576
|
+
RailsPgExtras.bloat
|
567
577
|
|
568
578
|
$ rake pg_extras:bloat
|
569
579
|
|
@@ -584,7 +594,7 @@ This command displays an estimation of table "bloat" – space allocated to a re
|
|
584
594
|
### `vacuum_stats`
|
585
595
|
|
586
596
|
```ruby
|
587
|
-
|
597
|
+
RailsPgExtras.vacuum_stats
|
588
598
|
|
589
599
|
$ rake pg_extras:vacuum_stats
|
590
600
|
|
@@ -603,7 +613,7 @@ This command displays statistics related to vacuum operations for each table, in
|
|
603
613
|
|
604
614
|
```ruby
|
605
615
|
|
606
|
-
|
616
|
+
RailsPgExtras.kill_all
|
607
617
|
|
608
618
|
```
|
609
619
|
|
@@ -612,7 +622,7 @@ This commands kills all the currently active connections to the database. It can
|
|
612
622
|
### `pg_stat_statements_reset`
|
613
623
|
|
614
624
|
```ruby
|
615
|
-
|
625
|
+
RailsPgExtras.pg_stat_statements_reset
|
616
626
|
```
|
617
627
|
|
618
628
|
This command discards all statistics gathered so far by pg_stat_statements.
|
@@ -620,7 +630,7 @@ This command discards all statistics gathered so far by pg_stat_statements.
|
|
620
630
|
### `buffercache_stats`
|
621
631
|
|
622
632
|
```ruby
|
623
|
-
|
633
|
+
RailsPgExtras.buffercache_stats(args: { limit: 10 })
|
624
634
|
```
|
625
635
|
|
626
636
|
This command shows the relations buffered in database share buffer, ordered by percentage taken. It also shows that how much of the whole relation is buffered.
|
@@ -628,7 +638,7 @@ This command shows the relations buffered in database share buffer, ordered by p
|
|
628
638
|
### `buffercache_usage`
|
629
639
|
|
630
640
|
```ruby
|
631
|
-
|
641
|
+
RailsPgExtras.buffercache_usage(args: { limit: 20 })
|
632
642
|
```
|
633
643
|
|
634
644
|
This command calculates how many blocks from which table are currently cached.
|
@@ -637,7 +647,7 @@ This command calculates how many blocks from which table are currently cached.
|
|
637
647
|
|
638
648
|
```ruby
|
639
649
|
|
640
|
-
|
650
|
+
RailsPgExtras.extensions
|
641
651
|
|
642
652
|
$ rake pg_extras:extensions
|
643
653
|
|
@@ -651,7 +661,7 @@ This command lists all the currently installed and available PostgreSQL extensio
|
|
651
661
|
### mandelbrot
|
652
662
|
|
653
663
|
```ruby
|
654
|
-
|
664
|
+
RailsPgExtras.mandelbrot
|
655
665
|
|
656
666
|
$ rake pg_extras:mandelbrot
|
657
667
|
```
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module RailsPgExtras::Web
|
2
|
+
class ActionsController < ApplicationController
|
3
|
+
def kill_all
|
4
|
+
run(:kill_all)
|
5
|
+
end
|
6
|
+
|
7
|
+
def pg_stat_statements_reset
|
8
|
+
run(:pg_stat_statements_reset)
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_extensions
|
12
|
+
run(:add_extensions)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def run(action)
|
18
|
+
begin
|
19
|
+
RailsPgExtras.run_query(query_name: action, in_format: :raw)
|
20
|
+
redirect_to root_path, notice: "Successfully ran #{action}"
|
21
|
+
rescue ActiveRecord::StatementInvalid => e
|
22
|
+
redirect_to root_path, alert: "Error: #{e.message}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rails_pg_extras"
|
2
|
+
require "rails_pg_extras/version"
|
3
|
+
|
4
|
+
module RailsPgExtras::Web
|
5
|
+
class ApplicationController < ActionController::Base
|
6
|
+
layout "rails_pg_extras/web/application"
|
7
|
+
|
8
|
+
REQUIRED_EXTENSIONS = {
|
9
|
+
pg_stat_statements: %i[calls outliers pg_stat_statements_reset],
|
10
|
+
pg_buffercache: %i[buffercache_stats buffercache_usage],
|
11
|
+
sslinfo: %i[ssl_used]
|
12
|
+
}
|
13
|
+
|
14
|
+
ACTIONS = %i[kill_all pg_stat_statements_reset add_extensions]
|
15
|
+
|
16
|
+
if Rails.env.production? && ENV['RAILS_PG_EXTRAS_USER'].present? && ENV['RAILS_PG_EXTRAS_PASSWORD'].present?
|
17
|
+
http_basic_authenticate_with name: ENV['RAILS_PG_EXTRAS_USER'], password: ENV['RAILS_PG_EXTRAS_PASSWORD']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RailsPgExtras::Web
|
2
|
+
class QueriesController < ApplicationController
|
3
|
+
before_action :load_queries
|
4
|
+
helper_method :unavailable_extensions
|
5
|
+
|
6
|
+
def index
|
7
|
+
if params[:query_name].present?
|
8
|
+
@query_name = params[:query_name].to_sym.presence_in(@all_queries.keys)
|
9
|
+
return unless @query_name
|
10
|
+
|
11
|
+
begin
|
12
|
+
@result = RailsPgExtras.run_query(query_name: @query_name.to_sym, in_format: :raw)
|
13
|
+
rescue ActiveRecord::StatementInvalid => e
|
14
|
+
@error = e.message
|
15
|
+
end
|
16
|
+
|
17
|
+
render :show
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def load_queries
|
24
|
+
@all_queries = (RailsPgExtras::QUERIES - ACTIONS).inject({}) do |memo, query_name|
|
25
|
+
unless query_name.in? %i[mandelbrot]
|
26
|
+
memo[query_name] = { disabled: query_disabled?(query_name) }
|
27
|
+
end
|
28
|
+
|
29
|
+
memo
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def query_disabled?(query_name)
|
34
|
+
unavailable_extensions.values.flatten.include?(query_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def unavailable_extensions
|
38
|
+
return @unavailable_extensions if defined?(@unavailable_extensions)
|
39
|
+
|
40
|
+
enabled_extensions = ActiveRecord::Base.connection.extensions
|
41
|
+
@unavailable_extensions = REQUIRED_EXTENSIONS.delete_if { |ext| ext.to_s.in?(enabled_extensions) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= content_for :title %> | v<%= RailsPgExtras::VERSION %></title>
|
5
|
+
<%= javascript_include_tag "https://unpkg.com/@rails/ujs" %>
|
6
|
+
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body class="p-5 text-xs">
|
10
|
+
<% if flash[:notice] %>
|
11
|
+
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4" role="alert">
|
12
|
+
<p class="font-bold">Notice</p>
|
13
|
+
<p><%= flash[:notice] %></p>
|
14
|
+
</div>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<% if flash[:alert] %>
|
18
|
+
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" role="alert">
|
19
|
+
<p class="font-bold">Alert</p>
|
20
|
+
<p><%= flash[:alert] %></p>
|
21
|
+
</div>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<%= yield %>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<h1 class="font-bold text-xl my-5">Diagnose</h1>
|
2
|
+
<table class="w-full font-mono border-collapse border my-5">
|
3
|
+
<% RailsPgExtras.diagnose(in_format: :hash).each do |diagnosis| %>
|
4
|
+
<tr class="<%= diagnosis[:ok] ? 'bg-green-300' : 'bg-red-300' %>">
|
5
|
+
<td class='p-1 border font-bold'><%= diagnosis[:check_name] %></td>
|
6
|
+
<td class='p-1 border'><%= diagnosis[:message] %></td>
|
7
|
+
</tr>
|
8
|
+
<% end %>
|
9
|
+
</table>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<h1 class="font-bold text-xl my-5"><%= title %></h1>
|
2
|
+
|
3
|
+
<table class="w-full font-mono border-collapse border my-5">
|
4
|
+
<thead>
|
5
|
+
<tr class="bg-gray-300">
|
6
|
+
<% headers.each do |header| %>
|
7
|
+
<th class="p-2 border text-left"><%= header %></th>
|
8
|
+
<% end %>
|
9
|
+
</tr>
|
10
|
+
</thead>
|
11
|
+
<tbody>
|
12
|
+
<% rows.each.with_index do |row, i| %>
|
13
|
+
<tr class="hover:bg-gray-400 hover:text-white <%= i.even? ? "bg-gray-100" : "bg-gray-200" %>">
|
14
|
+
<% row.each do |column| %>
|
15
|
+
<td class="p-1 border"><%= column %></td>
|
16
|
+
<% end %>
|
17
|
+
</tr>
|
18
|
+
<% end %>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
21
|
+
<span class="italic">run_at: <%= Time.now.utc %></span>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<div class="text-red-500 p-3 font-mono my-5">
|
2
|
+
<% unavailable_extensions.each do |extension, queries| %>
|
3
|
+
WARNING: Queries <%= queries.map { |q| "<b><u>#{q}</u></b>" }.join(", ").html_safe %> require extension: <b><%= extension %></b>
|
4
|
+
<br>
|
5
|
+
<% end %>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<%= link_to "Enable extensions", add_extensions_action_path,
|
9
|
+
method: "post",
|
10
|
+
data: {
|
11
|
+
confirm: "This command will enable following extensions: #{unavailable_extensions.keys.join(', ')}. Do you want to proceeed?"
|
12
|
+
}, class: 'border p-3 bg-green-500 text-white hover:bg-green-600 font-bold rounded' %>
|
13
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<%= content_for :title, "pg_extras" %>
|
2
|
+
<%= render "rails_pg_extras/web/shared/queries_selector" %>
|
3
|
+
<%= render "unavailable_extensions_warning" if unavailable_extensions.any? %>
|
4
|
+
<%= render "diagnose" %>
|
5
|
+
|
6
|
+
<h1 class="font-bold text-xl my-5">Actions</h1>
|
7
|
+
|
8
|
+
<%= link_to "kill_all", kill_all_action_path,
|
9
|
+
method: "post",
|
10
|
+
data: {
|
11
|
+
confirm: "This commands kills all the currently active connections to the database. Do you want to proceed?"
|
12
|
+
},
|
13
|
+
class: 'border p-3 bg-red-500 text-white hover:bg-red-600 font-bold rounded'
|
14
|
+
%>
|
15
|
+
|
16
|
+
<%= link_to "pg_stat_statements_reset", pg_stat_statements_reset_action_path,
|
17
|
+
method: "post",
|
18
|
+
data: {
|
19
|
+
confirm: "This command discards all statistics gathered so far by pg_stat_statements. Do you want to proceed?"
|
20
|
+
}, class: 'border p-3 bg-blue-500 text-white hover:bg-blue-600 font-bold rounded'
|
21
|
+
%>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<%= content_for :title, params[:query_name].presence || "pg_extras" %>
|
2
|
+
<%= render "rails_pg_extras/web/shared/queries_selector" %>
|
3
|
+
|
4
|
+
<% if @error %>
|
5
|
+
<div class="text-red-500 p-3 font-mono my-5"><%= @error %></div>
|
6
|
+
<% else %>
|
7
|
+
<% if @result&.any? %>
|
8
|
+
<%= render "result",
|
9
|
+
title: RubyPgExtras.description_for(query_name: @query_name),
|
10
|
+
headers: @result[0].keys,
|
11
|
+
rows: @result.values
|
12
|
+
%>
|
13
|
+
<% else %>
|
14
|
+
<div class="font-mono p-3 bg-gray-100 mt-3">No results</div>
|
15
|
+
<% end %>
|
16
|
+
<% end %>
|
17
|
+
|
18
|
+
<style>
|
19
|
+
@media print {
|
20
|
+
form {
|
21
|
+
display: none
|
22
|
+
}
|
23
|
+
}
|
24
|
+
</style>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= form_with url: queries_path, id: "queries", method: :get do |f| %>
|
2
|
+
<%= f.select :query_name, options_for_select(@all_queries, params[:query_name]),
|
3
|
+
{prompt: "--- select query ---"},
|
4
|
+
{class: "border p-2 font-bold", autofocus: true}
|
5
|
+
%>
|
6
|
+
<% end %>
|
7
|
+
<script>
|
8
|
+
document.getElementById('queries').addEventListener('change', (e) => {
|
9
|
+
e.target.form.submit()
|
10
|
+
})
|
11
|
+
</script>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
RailsPgExtras::Web::Engine.routes.draw do
|
2
|
+
resources :queries, only: [:index]
|
3
|
+
|
4
|
+
post "/actions/kill_all" => "actions#kill_all", as: :kill_all_action
|
5
|
+
post "/actions/pg_stat_statements_reset" => "actions#pg_stat_statements_reset", as: :pg_stat_statements_reset_action
|
6
|
+
post "/actions/add_extensions" => "actions#add_extensions", as: :add_extensions_action
|
7
|
+
|
8
|
+
root to: "queries#index"
|
9
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'rails_pg_extras'
|
4
4
|
|
5
5
|
namespace :pg_extras do
|
6
6
|
task :establish_connection do
|
@@ -13,25 +13,25 @@ namespace :pg_extras do
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
desc
|
16
|
+
RailsPgExtras::QUERIES.each do |query_name|
|
17
|
+
desc RubyPgExtras.description_for(query_name: query_name)
|
18
18
|
task query_name.to_sym => :establish_connection do
|
19
|
-
|
19
|
+
RailsPgExtras.public_send(query_name)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
desc "Generate a PostgreSQL healthcheck report"
|
24
24
|
task diagnose: :establish_connection do
|
25
|
-
|
25
|
+
RailsPgExtras.diagnose
|
26
26
|
end
|
27
27
|
|
28
28
|
desc "Display tables metadata metrics"
|
29
29
|
task table_info: :establish_connection do
|
30
|
-
|
30
|
+
RailsPgExtras.table_info
|
31
31
|
end
|
32
32
|
|
33
33
|
desc "Display indexes metadata metrics"
|
34
34
|
task index_info: :establish_connection do
|
35
|
-
|
35
|
+
RailsPgExtras.index_info
|
36
36
|
end
|
37
37
|
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'terminal-table'
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require '
|
7
|
-
require '
|
8
|
-
require '
|
9
|
-
require '
|
10
|
-
require '
|
4
|
+
require 'ruby_pg_extras'
|
5
|
+
require 'rails_pg_extras/diagnose_data'
|
6
|
+
require 'rails_pg_extras/diagnose_print'
|
7
|
+
require 'rails_pg_extras/index_info'
|
8
|
+
require 'rails_pg_extras/index_info_print'
|
9
|
+
require 'rails_pg_extras/table_info'
|
10
|
+
require 'rails_pg_extras/table_info_print'
|
11
|
+
require 'rails_pg_extras/web'
|
11
12
|
|
12
|
-
module
|
13
|
-
QUERIES =
|
14
|
-
DEFAULT_ARGS =
|
15
|
-
NEW_PG_STAT_STATEMENTS =
|
13
|
+
module RailsPgExtras
|
14
|
+
QUERIES = RubyPgExtras::QUERIES
|
15
|
+
DEFAULT_ARGS = RubyPgExtras::DEFAULT_ARGS
|
16
|
+
NEW_PG_STAT_STATEMENTS = RubyPgExtras::NEW_PG_STAT_STATEMENTS
|
16
17
|
|
17
18
|
QUERIES.each do |query_name|
|
18
19
|
define_singleton_method query_name do |options = {}|
|
@@ -26,7 +27,7 @@ module RailsPGExtras
|
|
26
27
|
|
27
28
|
def self.run_query(query_name:, in_format:, args: {})
|
28
29
|
if %i(calls outliers).include?(query_name)
|
29
|
-
pg_stat_statements_ver =
|
30
|
+
pg_stat_statements_ver = RailsPgExtras.connection.execute("select installed_version from pg_available_extensions where name='pg_stat_statements'")
|
30
31
|
.to_a[0].fetch("installed_version", nil)
|
31
32
|
if pg_stat_statements_ver != nil
|
32
33
|
if Gem::Version.new(pg_stat_statements_ver) < Gem::Version.new(NEW_PG_STAT_STATEMENTS)
|
@@ -36,25 +37,25 @@ module RailsPGExtras
|
|
36
37
|
end
|
37
38
|
|
38
39
|
sql = if (custom_args = DEFAULT_ARGS[query_name].merge(args)) != {}
|
39
|
-
|
40
|
+
RubyPgExtras.sql_for(query_name: query_name) % custom_args
|
40
41
|
else
|
41
|
-
|
42
|
+
RubyPgExtras.sql_for(query_name: query_name)
|
42
43
|
end
|
43
44
|
|
44
45
|
result = connection.execute(sql)
|
45
46
|
|
46
|
-
|
47
|
+
RubyPgExtras.display_result(
|
47
48
|
result,
|
48
|
-
title:
|
49
|
+
title: RubyPgExtras.description_for(query_name: query_name),
|
49
50
|
in_format: in_format
|
50
51
|
)
|
51
52
|
end
|
52
53
|
|
53
54
|
def self.diagnose(in_format: :display_table)
|
54
|
-
data =
|
55
|
+
data = RailsPgExtras::DiagnoseData.call
|
55
56
|
|
56
57
|
if in_format == :display_table
|
57
|
-
|
58
|
+
RailsPgExtras::DiagnosePrint.call(data)
|
58
59
|
elsif in_format == :hash
|
59
60
|
data
|
60
61
|
else
|
@@ -63,10 +64,10 @@ module RailsPGExtras
|
|
63
64
|
end
|
64
65
|
|
65
66
|
def self.index_info(args: {}, in_format: :display_table)
|
66
|
-
data =
|
67
|
+
data = RailsPgExtras::IndexInfo.call(args[:table_name])
|
67
68
|
|
68
69
|
if in_format == :display_table
|
69
|
-
|
70
|
+
RailsPgExtras::IndexInfoPrint.call(data)
|
70
71
|
elsif in_format == :hash
|
71
72
|
data
|
72
73
|
elsif in_format == :array
|
@@ -77,10 +78,10 @@ module RailsPGExtras
|
|
77
78
|
end
|
78
79
|
|
79
80
|
def self.table_info(args: {}, in_format: :display_table)
|
80
|
-
data =
|
81
|
+
data = RailsPgExtras::TableInfo.call(args[:table_name])
|
81
82
|
|
82
83
|
if in_format == :display_table
|
83
|
-
|
84
|
+
RailsPgExtras::TableInfoPrint.call(data)
|
84
85
|
elsif in_format == :hash
|
85
86
|
data
|
86
87
|
elsif in_format == :array
|
@@ -95,4 +96,4 @@ module RailsPGExtras
|
|
95
96
|
end
|
96
97
|
end
|
97
98
|
|
98
|
-
require '
|
99
|
+
require 'rails_pg_extras/railtie' if defined?(Rails)
|
data/rails-pg-extras.gemspec
CHANGED
@@ -1,22 +1,26 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require '
|
4
|
+
require 'rails_pg_extras/version'
|
5
5
|
|
6
|
-
Gem::Specification.new do |
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "rails-pg-extras"
|
8
|
+
s.version = RailsPgExtras::VERSION
|
9
|
+
s.authors = ["pawurb"]
|
10
|
+
s.email = ["contact@pawelurbanek.com"]
|
11
|
+
s.summary = %q{ Rails PostgreSQL performance database insights }
|
12
|
+
s.description = %q{ Rails port of Heroku PG Extras. The goal of this project is to provide a powerful insights into PostgreSQL database for Ruby on Rails apps that are not using the default Heroku PostgreSQL plugin. }
|
13
|
+
s.homepage = "http://github.com/pawurb/rails-pg-extras"
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = s.files.grep(%r{^(spec)/})
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.license = "MIT"
|
18
|
+
s.add_dependency "ruby-pg-extras", RailsPgExtras::VERSION
|
19
|
+
s.add_dependency "rails"
|
20
|
+
s.add_development_dependency "rake"
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
|
23
|
+
if s.respond_to?(:metadata=)
|
24
|
+
s.metadata = { "rubygems_mfa_required" => "true" }
|
25
|
+
end
|
22
26
|
end
|
data/spec/smoke_spec.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
|
+
require 'rails_pg_extras'
|
4
5
|
|
5
|
-
describe
|
6
|
-
|
6
|
+
describe RailsPgExtras do
|
7
|
+
RailsPgExtras::QUERIES.each do |query_name|
|
7
8
|
it "#{query_name} query can be executed" do
|
8
9
|
expect do
|
9
|
-
|
10
|
+
RailsPgExtras.run_query(
|
10
11
|
query_name: query_name,
|
11
12
|
in_format: :hash
|
12
13
|
)
|
@@ -16,15 +17,15 @@ describe RailsPGExtras do
|
|
16
17
|
|
17
18
|
it "runs the custom methods" do
|
18
19
|
expect do
|
19
|
-
|
20
|
+
RailsPgExtras.diagnose(in_format: :hash)
|
20
21
|
end.not_to raise_error
|
21
22
|
|
22
23
|
expect do
|
23
|
-
|
24
|
+
RailsPgExtras.index_info(in_format: :hash)
|
24
25
|
end.not_to raise_error
|
25
26
|
|
26
27
|
expect do
|
27
|
-
|
28
|
+
RailsPgExtras.table_info(in_format: :hash)
|
28
29
|
end.not_to raise_error
|
29
30
|
end
|
30
31
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'bundler/setup'
|
5
5
|
require 'active_record'
|
6
|
-
require_relative '../lib/
|
6
|
+
require_relative '../lib/rails_pg_extras'
|
7
7
|
|
8
8
|
pg_version = ENV["PG_VERSION"]
|
9
9
|
|
@@ -24,9 +24,9 @@ RSpec.configure do |config|
|
|
24
24
|
ActiveRecord::Base.establish_connection(
|
25
25
|
ENV.fetch("DATABASE_URL")
|
26
26
|
)
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
RailsPgExtras.connection.execute("CREATE EXTENSION IF NOT EXISTS pg_stat_statements;")
|
28
|
+
RailsPgExtras.connection.execute("CREATE EXTENSION IF NOT EXISTS pg_buffercache;")
|
29
|
+
RailsPgExtras.connection.execute("CREATE EXTENSION IF NOT EXISTS sslinfo;")
|
30
30
|
end
|
31
31
|
|
32
32
|
config.after :suite do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-pg-extras
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-pg-extras
|
@@ -16,16 +16,16 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 4.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 4.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -81,17 +81,30 @@ files:
|
|
81
81
|
- LICENSE.txt
|
82
82
|
- README.md
|
83
83
|
- Rakefile
|
84
|
+
- app/controllers/rails_pg_extras/web/actions_controller.rb
|
85
|
+
- app/controllers/rails_pg_extras/web/application_controller.rb
|
86
|
+
- app/controllers/rails_pg_extras/web/queries_controller.rb
|
87
|
+
- app/views/layouts/rails_pg_extras/web/application.html.erb
|
88
|
+
- app/views/rails_pg_extras/web/queries/_diagnose.html.erb
|
89
|
+
- app/views/rails_pg_extras/web/queries/_result.html.erb
|
90
|
+
- app/views/rails_pg_extras/web/queries/_unavailable_extensions_warning.html.erb
|
91
|
+
- app/views/rails_pg_extras/web/queries/index.html.erb
|
92
|
+
- app/views/rails_pg_extras/web/queries/show.html.erb
|
93
|
+
- app/views/rails_pg_extras/web/shared/_queries_selector.html.erb
|
94
|
+
- config/routes.rb
|
84
95
|
- docker-compose.yml.sample
|
85
|
-
- lib/
|
86
|
-
- lib/
|
87
|
-
- lib/
|
88
|
-
- lib/
|
89
|
-
- lib/
|
90
|
-
- lib/
|
91
|
-
- lib/
|
92
|
-
- lib/
|
93
|
-
- lib/
|
94
|
-
- lib/
|
96
|
+
- lib/rails_pg_extras.rb
|
97
|
+
- lib/rails_pg_extras/diagnose_data.rb
|
98
|
+
- lib/rails_pg_extras/diagnose_print.rb
|
99
|
+
- lib/rails_pg_extras/index_info.rb
|
100
|
+
- lib/rails_pg_extras/index_info_print.rb
|
101
|
+
- lib/rails_pg_extras/railtie.rb
|
102
|
+
- lib/rails_pg_extras/table_info.rb
|
103
|
+
- lib/rails_pg_extras/table_info_print.rb
|
104
|
+
- lib/rails_pg_extras/tasks/all.rake
|
105
|
+
- lib/rails_pg_extras/version.rb
|
106
|
+
- lib/rails_pg_extras/web.rb
|
107
|
+
- lib/rails_pg_extras/web/engine.rb
|
95
108
|
- rails-pg-extras-diagnose.png
|
96
109
|
- rails-pg-extras-web.png
|
97
110
|
- rails-pg-extras.gemspec
|
@@ -100,7 +113,8 @@ files:
|
|
100
113
|
homepage: http://github.com/pawurb/rails-pg-extras
|
101
114
|
licenses:
|
102
115
|
- MIT
|
103
|
-
metadata:
|
116
|
+
metadata:
|
117
|
+
rubygems_mfa_required: 'true'
|
104
118
|
post_install_message:
|
105
119
|
rdoc_options: []
|
106
120
|
require_paths:
|