ruby-pg-extras 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 771a487899452aae45b3491ef7b5c4704ff627abe1768c9342d64a6ae5c2e050
4
+ data.tar.gz: 98b7ccbc8b3c74e4b623be5cc932d73110676cf8de6610ceeffa0127c25c750b
5
+ SHA512:
6
+ metadata.gz: 4b7f48a2907f36db39033aa81801eef6cc397c8e2670024ad2b0f5d06984792bc7cfc664e3aab34b5f6d84268dc6bb312c345a078f7914fa858eb7dfa0aa1fe2
7
+ data.tar.gz: cf675a7c8364d01468602aee27961f008a33a6168ab11afd406cfaa7eb9b97c1197f6e0e455ba1801d9c4282d95502cb8041644b992e4455431e5eed6c5c4f1a
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ Gemfile.lock
2
+ .ruby-version
3
+ pkg/
4
+ *.gem
5
+ docker-compose.yml
6
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © Paweł Urbanek 2019
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,373 @@
1
+ # Rails PG Extras [![Gem Version](https://badge.fury.io/rb/ruby-pg-extras.svg)](https://badge.fury.io/rb/ruby-pg-extras) [![CircleCI](https://circleci.com/gh/pawurb/ruby-pg-extras.svg?style=svg)](https://circleci.com/gh/pawurb/ruby-pg-extras)
2
+
3
+ Rails port of [Heroku PG Extras](https://github.com/heroku/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.
4
+
5
+ Included rake tasks and Ruby methods can be used to obtain information about a Postgres instance, that may be useful when analyzing performance issues. This includes information about locks, index usage, buffer cache hit ratios and vacuum statistics. Ruby API enables developers to easily integrate the tool into e.g. automatic monitoring tasks.
6
+
7
+ ### Installation
8
+
9
+ In your Gemfile
10
+
11
+ ```ruby
12
+ gem 'ruby-pg-extras'
13
+ ```
14
+
15
+ ### Usage
16
+
17
+ Each command can be used as a rake task, or a directly from the Ruby code.
18
+
19
+ ```bash
20
+ rake pg_extras:cache_hit
21
+ ```
22
+
23
+ ```ruby
24
+ RubyPGExtras.cache_hit
25
+ ```
26
+ ```bash
27
+ +----------------+------------------------+
28
+ | Index and table hit rate |
29
+ +----------------+------------------------+
30
+ | name | ratio |
31
+ +----------------+------------------------+
32
+ | index hit rate | 0.97796610169491525424 |
33
+ | table hit rate | 0.96724294813466787989 |
34
+ +----------------+------------------------+
35
+ ```
36
+
37
+
38
+ 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):
39
+
40
+ ```ruby
41
+ RubyPGExtras.cache_hit(in_format: :hash) =>
42
+
43
+ [{"name"=>"index hit rate", "ratio"=>"0.97796610169491525424"}, {"name"=>"table hit rate", "ratio"=>"0.96724294813466787989"}]
44
+
45
+ RubyPGExtras.cache_hit(in_format: :array) =>
46
+
47
+ [["index hit rate", "0.97796610169491525424"], ["table hit rate", "0.96724294813466787989"]]
48
+
49
+ RubyPGExtras.cache_hit(in_format: :raw) =>
50
+
51
+ #<PG::Result:0x00007f75777f7328 status=PGRES_TUPLES_OK ntuples=2 nfields=2 cmd_tuples=2>
52
+ ```
53
+
54
+ ### Available methods
55
+
56
+ #### `cache_hit`
57
+
58
+ ```bash
59
+ $ rake pg_extras:cache_hit
60
+ name | ratio
61
+ ----------------+------------------------
62
+ index hit rate | 0.99957765013541945832
63
+ table hit rate | 1.00
64
+ (2 rows)
65
+ ```
66
+
67
+ This command provides information on the efficiency of the buffer cache, for both index reads (`index hit rate`) as well as table reads (`table hit rate`). A low buffer cache hit ratio can be a sign that the Postgres instance is too small for the workload.
68
+
69
+ #### `index_usage`
70
+
71
+ ```
72
+ $ rake pg_extras:index_usage
73
+ relname | percent_of_times_index_used | rows_in_table
74
+ ---------------------+-----------------------------+---------------
75
+ events | 65 | 1217347
76
+ app_infos | 74 | 314057
77
+ app_infos_user_info | 0 | 198848
78
+ user_info | 5 | 94545
79
+ delayed_jobs | 27 | 0
80
+ (5 rows)
81
+ ```
82
+
83
+ This command provides information on the efficiency of indexes, represented as what percentage of total scans were index scans. A low percentage can indicate under indexing, or wrong data being indexed.
84
+
85
+ ### `locks`
86
+
87
+ ```
88
+ $ rake pg_extras:locks
89
+ procpid | relname | transactionid | granted | query_snippet | age
90
+ ---------+---------+---------------+---------+-----------------------+-----------------
91
+ 31776 | | | t | <IDLE> in transaction | 00:19:29.837898
92
+ 31776 | | 1294 | t | <IDLE> in transaction | 00:19:29.837898
93
+ 31912 | | | t | select * from hello; | 00:19:17.94259
94
+ 3443 | | | t | +| 00:00:00
95
+ | | | | select +|
96
+ | | | | pg_stat_activi |
97
+ (4 rows)
98
+ ```
99
+
100
+ This command displays queries that have taken out an exlusive lock on a relation. Exclusive locks typically prevent other operations on that relation from taking place, and can be a cause of "hung" queries that are waiting for a lock to be granted.
101
+
102
+ ### `all_locks`
103
+
104
+ ```
105
+ $ rake pg_extras:all_locks
106
+ ```
107
+
108
+ This command displays all the current locks, regardless of their type.
109
+
110
+ ### `outliers`
111
+
112
+ ```
113
+ $ rake pg_extras:outliers
114
+ qry | exec_time | prop_exec_time | ncalls | sync_io_time
115
+ -----------------------------------------+------------------+----------------+-------------+--------------
116
+ SELECT * FROM archivable_usage_events.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00
117
+ COPY public.archivable_usage_events (.. | 50:38:33.198418 | 23.6% | 13 | 13:34:21.00108
118
+ COPY public.usage_events (id, reporte.. | 02:32:16.335233 | 1.2% | 13 | 00:34:19.784318
119
+ INSERT INTO usage_events (id, retaine.. | 01:42:59.436532 | 0.8% | 12,328,187 | 00:00:00
120
+ SELECT * FROM usage_events WHERE (alp.. | 01:18:10.754354 | 0.6% | 102,114,301 | 00:00:00
121
+ UPDATE usage_events SET reporter_id =.. | 00:52:35.683254 | 0.4% | 23,786,348 | 00:00:00
122
+ INSERT INTO usage_events (id, retaine.. | 00:49:24.952561 | 0.4% | 21,988,201 | 00:00:00
123
+ COPY public.app_ownership_events (id,.. | 00:37:14.31082 | 0.3% | 13 | 00:12:32.584754
124
+ INSERT INTO app_ownership_events (id,.. | 00:26:59.808212 | 0.2% | 383,109 | 00:00:00
125
+ SELECT * FROM app_ownership_events .. | 00:19:06.021846 | 0.1% | 744,879 | 00:00:00
126
+ (10 rows)
127
+ ```
128
+
129
+ This command displays statements, obtained from `pg_stat_statements`, ordered by the amount of time to execute in aggregate. This includes the statement itself, the total execution time for that statement, the proportion of total execution time for all statements that statement has taken up, the number of times that statement has been called, and the amount of time that statement spent on synchronous I/O (reading/writing from the filesystem).
130
+
131
+ Typically, an efficient query will have an appropriate ratio of calls to total execution time, with as little time spent on I/O as possible. Queries that have a high total execution time but low call count should be investigated to improve their performance. Queries that have a high proportion of execution time being spent on synchronous I/O should also be investigated.
132
+
133
+ ### `calls`
134
+
135
+ ```
136
+ $ rake pg_extras:calls
137
+ qry | exec_time | prop_exec_time | ncalls | sync_io_time
138
+ -----------------------------------------+------------------+----------------+-------------+--------------
139
+ SELECT * FROM usage_events WHERE (alp.. | 01:18:11.073333 | 0.6% | 102,120,780 | 00:00:00
140
+ BEGIN | 00:00:51.285988 | 0.0% | 47,288,662 | 00:00:00
141
+ COMMIT | 00:00:52.31724 | 0.0% | 47,288,615 | 00:00:00
142
+ SELECT * FROM archivable_usage_event.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00
143
+ UPDATE usage_events SET reporter_id =.. | 00:52:35.986167 | 0.4% | 23,788,388 | 00:00:00
144
+ INSERT INTO usage_events (id, retaine.. | 00:49:25.260245 | 0.4% | 21,990,326 | 00:00:00
145
+ INSERT INTO usage_events (id, retaine.. | 01:42:59.436532 | 0.8% | 12,328,187 | 00:00:00
146
+ SELECT * FROM app_ownership_events .. | 00:19:06.289521 | 0.1% | 744,976 | 00:00:00
147
+ INSERT INTO app_ownership_events(id, .. | 00:26:59.885631 | 0.2% | 383,153 | 00:00:00
148
+ UPDATE app_ownership_events SET app_i.. | 00:01:22.282337 | 0.0% | 359,741 | 00:00:00
149
+ (10 rows)
150
+ ```
151
+
152
+ This command is much like `pg:outliers`, but ordered by the number of times a statement has been called.
153
+
154
+ ### `blocking`
155
+
156
+ ```
157
+ $ rake pg_extras:blocking
158
+ blocked_pid | blocking_statement | blocking_duration | blocking_pid | blocked_statement | blocked_duration
159
+ -------------+--------------------------+-------------------+--------------+------------------------------------------------------------------------------------+------------------
160
+ 461 | select count(*) from app | 00:00:03.838314 | 15682 | UPDATE "app" SET "updated_at" = '2013-03-04 15:07:04.746688' WHERE "id" = 12823149 | 00:00:03.821826
161
+ (1 row)
162
+ ```
163
+
164
+ This command displays statements that are currently holding locks that other statements are waiting to be released. This can be used in conjunction with `pg:locks` to determine which statements need to be terminated in order to resolve lock contention.
165
+
166
+ #### `total_index_size`
167
+
168
+ ```
169
+ $ rake pg_extras:total_index_size
170
+ size
171
+ -------
172
+ 28194 MB
173
+ (1 row)
174
+ ```
175
+
176
+ This command displays the total size of all indexes on the database, in MB. It is calculated by taking the number of pages (reported in `relpages`) and multiplying it by the page size (8192 bytes).
177
+
178
+ ### `index_size`
179
+
180
+ ```
181
+ $ rake pg_extras:index_size
182
+ name | size
183
+ ---------------------------------------------------------------+---------
184
+ idx_activity_attemptable_and_type_lesson_enrollment | 5196 MB
185
+ index_enrollment_attemptables_by_attempt_and_last_in_group | 4045 MB
186
+ index_attempts_on_student_id | 2611 MB
187
+ enrollment_activity_attemptables_pkey | 2513 MB
188
+ index_attempts_on_student_id_final_attemptable_type | 2466 MB
189
+ attempts_pkey | 2466 MB
190
+ index_attempts_on_response_id | 2404 MB
191
+ index_attempts_on_enrollment_id | 1957 MB
192
+ index_enrollment_attemptables_by_enrollment_activity_id | 1789 MB
193
+ enrollment_activities_pkey | 458 MB
194
+ index_enrollment_activities_by_lesson_enrollment_and_activity | 402 MB
195
+ index_placement_attempts_on_response_id | 109 MB
196
+ index_placement_attempts_on_placement_test_id | 108 MB
197
+ index_placement_attempts_on_grade_level_id | 97 MB
198
+ index_lesson_enrollments_on_lesson_id | 93 MB
199
+ (truncated results for brevity)
200
+ ```
201
+
202
+ This command displays the size of each each index in the database, in MB. It is calculated by taking the number of pages (reported in `relpages`) and multiplying it by the page size (8192 bytes).
203
+
204
+ ### `table_size`
205
+
206
+ ```
207
+ $ rake pg_extras:table_size
208
+ name | size
209
+ ---------------------------------------------------------------+---------
210
+ learning_coaches | 196 MB
211
+ states | 145 MB
212
+ grade_levels | 111 MB
213
+ charities_customers | 73 MB
214
+ charities | 66 MB
215
+ (truncated results for brevity)
216
+ ```
217
+
218
+ This command displays the size of each table in the database, in MB. It is calculated by using the system administration function `pg_table_size()`, which includes the size of the main data fork, free space map, visibility map and TOAST data.
219
+
220
+ ### `table_indexes_size`
221
+
222
+ ```
223
+ $ rake pg_extras:table-indexes-size
224
+ table | indexes_size
225
+ ---------------------------------------------------------------+--------------
226
+ learning_coaches | 153 MB
227
+ states | 125 MB
228
+ charities_customers | 93 MB
229
+ charities | 16 MB
230
+ grade_levels | 11 MB
231
+ (truncated results for brevity)
232
+ ```
233
+
234
+ This command displays the total size of indexes for each table, in MB. It is calcualtes by using the system administration function `pg_indexes_size()`.
235
+
236
+ ### `total_table_size`
237
+
238
+ ```
239
+ $ rake pg_extras:total_table_size
240
+ name | size
241
+ ---------------------------------------------------------------+---------
242
+ learning_coaches | 349 MB
243
+ states | 270 MB
244
+ charities_customers | 166 MB
245
+ grade_levels | 122 MB
246
+ charities | 82 MB
247
+ (truncated results for brevity)
248
+ ```
249
+
250
+ This command displays the total size of each table in the database, in MB. It is calculated by using the system administration function `pg_total_relation_size()`, which includes table size, total index size and TOAST data.
251
+
252
+ ### `unused_indexes`
253
+
254
+ ```
255
+ $ rake pg_extras:unused_indexes
256
+ table | index | index_size | index_scans
257
+ ---------------------+--------------------------------------------+------------+-------------
258
+ public.grade_levels | index_placement_attempts_on_grade_level_id | 97 MB | 0
259
+ public.observations | observations_attrs_grade_resources | 33 MB | 0
260
+ public.messages | user_resource_id_idx | 12 MB | 0
261
+ (3 rows)
262
+ ```
263
+
264
+ This command displays indexes that have < 50 scans recorded against them, and are greater than 5 pages in size, ordered by size relative to the number of index scans. This command is generally useful for eliminating indexes that are unused, which can impact write performance, as well as read performance should they occupy space in memory.
265
+
266
+ ### `seq_scans`
267
+
268
+ ```
269
+ $ rake pg_extras:seq_scans
270
+
271
+ name | count
272
+ -----------------------------------+----------
273
+ learning_coaches | 44820063
274
+ states | 36794975
275
+ grade_levels | 13972293
276
+ charities_customers | 8615277
277
+ charities | 4316276
278
+ messages | 3922247
279
+ contests_customers | 2915972
280
+ classroom_goals | 2142014
281
+ contests | 1370267
282
+ goals | 1112659
283
+ districts | 158995
284
+ rollup_reports | 115942
285
+ customers | 93847
286
+ schools | 92984
287
+ classrooms | 92982
288
+ customer_settings | 91226
289
+ (truncated results for brevity)
290
+ ```
291
+
292
+ This command displays the number of sequential scans recorded against all tables, descending by count of sequential scans. Tables that have very high numbers of sequential scans may be underindexed, and it may be worth investigating queries that read from these tables.
293
+
294
+ ### long_running_queries
295
+
296
+ ```
297
+ $ rake pg_extras:long_running_queries
298
+
299
+ pid | duration | query
300
+ -------+-----------------+---------------------------------------------------------------------------------------
301
+ 19578 | 02:29:11.200129 | EXPLAIN SELECT "students".* FROM "students" WHERE "students"."id" = 1450645 LIMIT 1
302
+ 19465 | 02:26:05.542653 | EXPLAIN SELECT "students".* FROM "students" WHERE "students"."id" = 1889881 LIMIT 1
303
+ 19632 | 02:24:46.962818 | EXPLAIN SELECT "students".* FROM "students" WHERE "students"."id" = 1581884 LIMIT 1
304
+ (truncated results for brevity)
305
+ ```
306
+
307
+ This command displays currently running queries, that have been running for longer than 5 minutes, descending by duration. Very long running queries can be a source of multiple issues, such as preventing DDL statements completing or vacuum being unable to update `relfrozenxid`.
308
+
309
+ ### records_rank
310
+
311
+ ```
312
+ $ rake pg_extras:records_rank
313
+ name | estimated_count
314
+ -----------------------------------+-----------------
315
+ tastypie_apiaccess | 568891
316
+ notifications_event | 381227
317
+ core_todo | 178614
318
+ core_comment | 123969
319
+ notifications_notification | 102101
320
+ django_session | 68078
321
+ (truncated results for brevity)
322
+ ```
323
+
324
+ This command displays an estimated count of rows per table, descending by estimated count. The estimated count is derived from `n_live_tup`, which is updated by vacuum operations. Due to the way `n_live_tup` is populated, sparse vs. dense pages can result in estimations that are significantly out from the real count of rows.
325
+
326
+ ### bloat
327
+
328
+ ```
329
+ $ rake pg_extras:bloat
330
+
331
+ type | schemaname | object_name | bloat | waste
332
+ -------+------------+-------------------------------+-------+----------
333
+ table | public | bloated_table | 1.1 | 98 MB
334
+ table | public | other_bloated_table | 1.1 | 58 MB
335
+ index | public | bloated_table::bloated_index | 3.7 | 34 MB
336
+ table | public | clean_table | 0.2 | 3808 kB
337
+ table | public | other_clean_table | 0.3 | 1576 kB
338
+ ```
339
+
340
+ This command displays an estimation of table "bloat" – space allocated to a relation that is full of dead tuples, that has yet to be reclaimed. Tables that have a high bloat ratio, typically 10 or greater, should be investigated to see if vacuuming is aggressive enough, and can be a sign of high table churn.
341
+
342
+ ### vacuum_stats
343
+
344
+ ```
345
+ $ rake pg_extras:vacuum_stats
346
+ schema | table | last_vacuum | last_autovacuum | rowcount | dead_rowcount | autovacuum_threshold | expect_autovacuum
347
+ --------+-----------------------+-------------+------------------+----------------+----------------+----------------------+-------------------
348
+ public | log_table | | 2013-04-26 17:37 | 18,030 | 0 | 3,656 |
349
+ public | data_table | | 2013-04-26 13:09 | 79 | 28 | 66 |
350
+ public | other_table | | 2013-04-26 11:41 | 41 | 47 | 58 |
351
+ public | queue_table | | 2013-04-26 17:39 | 12 | 8,228 | 52 | yes
352
+ public | picnic_table | | | 13 | 0 | 53 |
353
+ ```
354
+
355
+ This command displays statistics related to vacuum operations for each table, including an estiamtion of dead rows, last autovacuum and the current autovacuum threshold. This command can be useful when determining if current vacuum thresholds require adjustments, and to determine when the table was last vacuumed.
356
+
357
+ ### mandelbrot
358
+
359
+ ```
360
+ $ rake pg_extras:mandelbrot
361
+ ```
362
+
363
+ This command outputs the Mandelbrot set, calculated through SQL.
364
+
365
+ ## FAQ
366
+
367
+ * Does is not violate the Heroku PG Extras license?
368
+
369
+ The original plugin is MIT based so it means that copying and redistribution in any format is permitted.
370
+
371
+ ## Disclaimer
372
+
373
+ This tool is in beta state.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'terminal-table'
4
+
5
+ module RubyPGExtras
6
+ QUERIES = %i(
7
+ bloat blocking cache_hit
8
+ calls extensions
9
+ index_size index_usage locks all_locks
10
+ long_running_queries mandelbrot outliers
11
+ records_rank seq_scans table_indexes_size
12
+ table_size total_index_size total_table_size
13
+ unused_indexes vacuum_stats
14
+ )
15
+
16
+ QUERIES.each do |query_name|
17
+ define_singleton_method query_name do |options = { in_format: :display_table }|
18
+ run_query(
19
+ query_name: query_name,
20
+ in_format: options.fetch(:in_format)
21
+ )
22
+ end
23
+ end
24
+
25
+ def self.run_query(query_name:, in_format:)
26
+ result = connection.execute(
27
+ sql_for(query_name: query_name)
28
+ )
29
+
30
+ display_result(
31
+ result,
32
+ title: description_for(query_name: query_name),
33
+ in_format: in_format
34
+ )
35
+ end
36
+
37
+ def self.display_result(result, title:, in_format:)
38
+ case in_format
39
+ when :array
40
+ result.values
41
+ when :hash
42
+ result.to_a
43
+ when :raw
44
+ result
45
+ when :display_table
46
+ headings = if result.count > 0
47
+ result[0].keys
48
+ else
49
+ ["No results"]
50
+ end
51
+
52
+ puts Terminal::Table.new(
53
+ title: title,
54
+ headings: headings,
55
+ rows: result.values
56
+ )
57
+ else
58
+ raise "Invalid in_format option"
59
+ end
60
+ end
61
+
62
+ def self.description_for(query_name:)
63
+ first_line = File.open(
64
+ sql_path_for(query_name: query_name)
65
+ ) { |f| f.readline }
66
+
67
+ first_line[/\/\*(.*?)\*\//m, 1].strip
68
+ end
69
+
70
+ def self.sql_for(query_name:)
71
+ File.read(
72
+ sql_path_for(query_name: query_name)
73
+ )
74
+ end
75
+
76
+ def self.sql_path_for(query_name:)
77
+ File.join(File.dirname(__FILE__), "/ruby-pg-extras/queries/#{query_name}.sql")
78
+ end
79
+
80
+ def self.connection
81
+ ActiveRecord::Base.connection
82
+ end
83
+
84
+ %i(
85
+ connection
86
+ display_result
87
+ sql_for
88
+ sql_path_for
89
+ ).each do |method_name|
90
+ private_class_method method_name
91
+ end
92
+ end
93
+
94
+ require 'ruby-pg-extras/railtie' if defined?(Rails)
@@ -0,0 +1,16 @@
1
+ /* Queries with active locks */
2
+
3
+ SELECT
4
+ pg_stat_activity.pid,
5
+ pg_class.relname,
6
+ pg_locks.transactionid,
7
+ pg_locks.granted,
8
+ pg_locks.mode,
9
+ pg_stat_activity.query AS query_snippet,
10
+ age(now(),pg_stat_activity.query_start) AS "age"
11
+ FROM pg_stat_activity,pg_locks left
12
+ OUTER JOIN pg_class
13
+ ON (pg_locks.relation = pg_class.oid)
14
+ WHERE pg_stat_activity.query <> '<insufficient privilege>'
15
+ AND pg_locks.pid = pg_stat_activity.pid
16
+ AND pg_stat_activity.pid <> pg_backend_pid() order by query_start;
@@ -0,0 +1,63 @@
1
+ /* Table and index bloat in your database ordered by most wasteful */
2
+
3
+ WITH constants AS (
4
+ SELECT current_setting('block_size')::numeric AS bs, 23 AS hdr, 4 AS ma
5
+ ), bloat_info AS (
6
+ SELECT
7
+ ma,bs,schemaname,tablename,
8
+ (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
9
+ (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
10
+ FROM (
11
+ SELECT
12
+ schemaname, tablename, hdr, ma, bs,
13
+ SUM((1-null_frac)*avg_width) AS datawidth,
14
+ MAX(null_frac) AS maxfracsum,
15
+ hdr+(
16
+ SELECT 1+count(*)/8
17
+ FROM pg_stats s2
18
+ WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
19
+ ) AS nullhdr
20
+ FROM pg_stats s, constants
21
+ GROUP BY 1,2,3,4,5
22
+ ) AS foo
23
+ ), table_bloat AS (
24
+ SELECT
25
+ schemaname, tablename, cc.relpages, bs,
26
+ CEIL((cc.reltuples*((datahdr+ma-
27
+ (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)) AS otta
28
+ FROM bloat_info
29
+ JOIN pg_class cc ON cc.relname = bloat_info.tablename
30
+ JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
31
+ ), index_bloat AS (
32
+ SELECT
33
+ schemaname, tablename, bs,
34
+ COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
35
+ COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
36
+ FROM bloat_info
37
+ JOIN pg_class cc ON cc.relname = bloat_info.tablename
38
+ JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
39
+ JOIN pg_index i ON indrelid = cc.oid
40
+ JOIN pg_class c2 ON c2.oid = i.indexrelid
41
+ )
42
+ SELECT
43
+ type, schemaname, object_name, bloat, pg_size_pretty(raw_waste) as waste
44
+ FROM
45
+ (SELECT
46
+ 'table' as type,
47
+ schemaname,
48
+ tablename as object_name,
49
+ ROUND(CASE WHEN otta=0 THEN 0.0 ELSE table_bloat.relpages/otta::numeric END,1) AS bloat,
50
+ CASE WHEN relpages < otta THEN '0' ELSE (bs*(table_bloat.relpages-otta)::bigint)::bigint END AS raw_waste
51
+ FROM
52
+ table_bloat
53
+ UNION
54
+ SELECT
55
+ 'index' as type,
56
+ schemaname,
57
+ tablename || '::' || iname as object_name,
58
+ ROUND(CASE WHEN iotta=0 OR ipages=0 THEN 0.0 ELSE ipages/iotta::numeric END,1) AS bloat,
59
+ CASE WHEN ipages < iotta THEN '0' ELSE (bs*(ipages-iotta))::bigint END AS raw_waste
60
+ FROM
61
+ index_bloat) bloat_summary
62
+ ORDER BY raw_waste DESC, bloat DESC;
63
+
@@ -0,0 +1,16 @@
1
+ /* Queries holding locks other queries are waiting to be released */
2
+
3
+ SELECT bl.pid AS blocked_pid,
4
+ ka.query AS blocking_statement,
5
+ now() - ka.query_start AS blocking_duration,
6
+ kl.pid AS blocking_pid,
7
+ a.query AS blocked_statement,
8
+ now() - a.query_start AS blocked_duration
9
+ FROM pg_catalog.pg_locks bl
10
+ JOIN pg_catalog.pg_stat_activity a
11
+ ON bl.pid = a.pid
12
+ JOIN pg_catalog.pg_locks kl
13
+ JOIN pg_catalog.pg_stat_activity ka
14
+ ON kl.pid = ka.pid
15
+ ON bl.transactionid = kl.transactionid AND bl.pid != kl.pid
16
+ WHERE NOT bl.granted;
@@ -0,0 +1,11 @@
1
+ /* Index and table hit rate */
2
+
3
+ SELECT
4
+ 'index hit rate' AS name,
5
+ (sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS ratio
6
+ FROM pg_statio_user_indexes
7
+ UNION ALL
8
+ SELECT
9
+ 'table hit rate' AS name,
10
+ sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS ratio
11
+ FROM pg_statio_user_tables;
@@ -0,0 +1,9 @@
1
+ /* 10 queries that have longest execution time in aggregate */
2
+
3
+ SELECT query AS qry,
4
+ interval '1 millisecond' * total_time AS exec_time,
5
+ to_char((total_time/sum(total_time) OVER()) * 100, 'FM90D0') || '%' AS prop_exec_time,
6
+ to_char(calls, 'FM999G999G990') AS ncalls,
7
+ interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
8
+ FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
9
+ ORDER BY calls DESC LIMIT 10;
@@ -0,0 +1,4 @@
1
+ /* Available and installed extensions */
2
+
3
+ SELECT * FROM pg_available_extensions ORDER BY installed_version;
4
+
@@ -0,0 +1,11 @@
1
+ /* The size of indexes, descending by size */
2
+
3
+ SELECT c.relname AS name,
4
+ pg_size_pretty(sum(c.relpages::bigint*8192)::bigint) AS size
5
+ FROM pg_class c
6
+ LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
7
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
8
+ AND n.nspname !~ '^pg_toast'
9
+ AND c.relkind='i'
10
+ GROUP BY c.relname
11
+ ORDER BY sum(c.relpages) DESC;
@@ -0,0 +1,12 @@
1
+ /* Index hit rate (effective databases are at 99% and up) */
2
+
3
+ SELECT relname,
4
+ CASE idx_scan
5
+ WHEN 0 THEN 'Insufficient data'
6
+ ELSE (100 * idx_scan / (seq_scan + idx_scan))::text
7
+ END percent_of_times_index_used,
8
+ n_live_tup rows_in_table
9
+ FROM
10
+ pg_stat_user_tables
11
+ ORDER BY
12
+ n_live_tup DESC;
@@ -0,0 +1,16 @@
1
+ /* Queries with active exclusive locks */
2
+
3
+ SELECT
4
+ pg_stat_activity.pid,
5
+ pg_class.relname,
6
+ pg_locks.transactionid,
7
+ pg_locks.granted,
8
+ pg_stat_activity.query AS query_snippet,
9
+ age(now(),pg_stat_activity.query_start) AS "age"
10
+ FROM pg_stat_activity,pg_locks left
11
+ OUTER JOIN pg_class
12
+ ON (pg_locks.relation = pg_class.oid)
13
+ WHERE pg_stat_activity.query <> '<insufficient privilege>'
14
+ AND pg_locks.pid = pg_stat_activity.pid
15
+ AND pg_locks.mode = 'ExclusiveLock'
16
+ AND pg_stat_activity.pid <> pg_backend_pid() order by query_start;
@@ -0,0 +1,14 @@
1
+ /* All queries longer than five minutes by descending duration */
2
+
3
+ SELECT
4
+ pid,
5
+ now() - pg_stat_activity.query_start AS duration,
6
+ query AS query
7
+ FROM
8
+ pg_stat_activity
9
+ WHERE
10
+ pg_stat_activity.query <> ''::text
11
+ AND state <> 'idle'
12
+ AND now() - pg_stat_activity.query_start > interval '5 minutes'
13
+ ORDER BY
14
+ now() - pg_stat_activity.query_start DESC;
@@ -0,0 +1,21 @@
1
+ /* The mandelbrot set */
2
+
3
+ WITH RECURSIVE Z(IX, IY, CX, CY, X, Y, I) AS (
4
+ SELECT IX, IY, X::float, Y::float, X::float, Y::float, 0
5
+ FROM (select -2.2 + 0.031 * i, i from generate_series(0,101) as i) as xgen(x,ix),
6
+ (select -1.5 + 0.031 * i, i from generate_series(0,101) as i) as ygen(y,iy)
7
+ UNION ALL
8
+ SELECT IX, IY, CX, CY, X * X - Y * Y + CX AS X, Y * X * 2 + CY, I + 1
9
+ FROM Z
10
+ WHERE X * X + Y * Y < 16::float
11
+ AND I < 100
12
+ )
13
+ SELECT array_to_string(array_agg(SUBSTRING(' .,,,-----++++%%%%@@@@#### ', LEAST(GREATEST(I,1),27), 1)),'')
14
+ FROM (
15
+ SELECT IX, IY, MAX(I) AS I
16
+ FROM Z
17
+ GROUP BY IY, IX
18
+ ORDER BY IY, IX
19
+ ) AS ZT
20
+ GROUP BY IY
21
+ ORDER BY IY;
@@ -0,0 +1,10 @@
1
+ /* 10 queries that have longest execution time in aggregate */
2
+
3
+ SELECT interval '1 millisecond' * total_time AS total_exec_time,
4
+ to_char((total_time/sum(total_time) OVER()) * 100, 'FM90D0') || '%' AS prop_exec_time,
5
+ to_char(calls, 'FM999G999G999G990') AS ncalls,
6
+ interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time,
7
+ query AS query
8
+ FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
9
+ ORDER BY total_time DESC
10
+ LIMIT 10;
@@ -0,0 +1,9 @@
1
+ /* All tables and the number of rows in each ordered by number of rows descending */
2
+
3
+ SELECT
4
+ relname AS name,
5
+ n_live_tup AS estimated_count
6
+ FROM
7
+ pg_stat_user_tables
8
+ ORDER BY
9
+ n_live_tup DESC;
@@ -0,0 +1,7 @@
1
+ /* Count of sequential scans by table descending by order */
2
+
3
+ SELECT relname AS name,
4
+ seq_scan as count
5
+ FROM
6
+ pg_stat_user_tables
7
+ ORDER BY seq_scan DESC;
@@ -0,0 +1,10 @@
1
+ /* Total size of all the indexes on each table, descending by size */
2
+
3
+ SELECT c.relname AS table,
4
+ pg_size_pretty(pg_indexes_size(c.oid)) AS index_size
5
+ FROM pg_class c
6
+ LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
7
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
8
+ AND n.nspname !~ '^pg_toast'
9
+ AND c.relkind='r'
10
+ ORDER BY pg_indexes_size(c.oid) DESC;
@@ -0,0 +1,10 @@
1
+ /* Size of the tables (excluding indexes), descending by size */
2
+
3
+ SELECT c.relname AS name,
4
+ pg_size_pretty(pg_table_size(c.oid)) AS size
5
+ FROM pg_class c
6
+ LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
7
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
8
+ AND n.nspname !~ '^pg_toast'
9
+ AND c.relkind='r'
10
+ ORDER BY pg_table_size(c.oid) DESC;
@@ -0,0 +1,8 @@
1
+ /* Total size of all indexes in MB */
2
+
3
+ SELECT pg_size_pretty(sum(c.relpages::bigint*8192)::bigint) AS size
4
+ FROM pg_class c
5
+ LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
6
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
7
+ AND n.nspname !~ '^pg_toast'
8
+ AND c.relkind='i';
@@ -0,0 +1,10 @@
1
+ /* Size of the tables (including indexes), descending by size */
2
+
3
+ SELECT c.relname AS name,
4
+ pg_size_pretty(pg_total_relation_size(c.oid)) AS size
5
+ FROM pg_class c
6
+ LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
7
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
8
+ AND n.nspname !~ '^pg_toast'
9
+ AND c.relkind='r'
10
+ ORDER BY pg_total_relation_size(c.oid) DESC;
@@ -0,0 +1,16 @@
1
+ /* Unused and almost unused indexes */
2
+ /* Ordered by their size relative to the number of index scans.
3
+ Exclude indexes of very small tables (less than 5 pages),
4
+ where the planner will almost invariably select a sequential scan,
5
+ but may not in the future as the table grows */
6
+
7
+ SELECT
8
+ schemaname || '.' || relname AS table,
9
+ indexrelname AS index,
10
+ pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
11
+ idx_scan as index_scans
12
+ FROM pg_stat_user_indexes ui
13
+ JOIN pg_index i ON ui.indexrelid = i.indexrelid
14
+ WHERE NOT indisunique AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 8192
15
+ ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
16
+ pg_relation_size(i.indexrelid) DESC;
@@ -0,0 +1,40 @@
1
+ /* Dead rows and whether an automatic vacuum is expected to be triggered */
2
+
3
+ WITH table_opts AS (
4
+ SELECT
5
+ pg_class.oid, relname, nspname, array_to_string(reloptions, '') AS relopts
6
+ FROM
7
+ pg_class INNER JOIN pg_namespace ns ON relnamespace = ns.oid
8
+ ), vacuum_settings AS (
9
+ SELECT
10
+ oid, relname, nspname,
11
+ CASE
12
+ WHEN relopts LIKE '%autovacuum_vacuum_threshold%'
13
+ THEN substring(relopts, '.*autovacuum_vacuum_threshold=([0-9.]+).*')::integer
14
+ ELSE current_setting('autovacuum_vacuum_threshold')::integer
15
+ END AS autovacuum_vacuum_threshold,
16
+ CASE
17
+ WHEN relopts LIKE '%autovacuum_vacuum_scale_factor%'
18
+ THEN substring(relopts, '.*autovacuum_vacuum_scale_factor=([0-9.]+).*')::real
19
+ ELSE current_setting('autovacuum_vacuum_scale_factor')::real
20
+ END AS autovacuum_vacuum_scale_factor
21
+ FROM
22
+ table_opts
23
+ )
24
+ SELECT
25
+ vacuum_settings.nspname AS schema,
26
+ vacuum_settings.relname AS table,
27
+ to_char(psut.last_vacuum, 'YYYY-MM-DD HH24:MI') AS last_vacuum,
28
+ to_char(psut.last_autovacuum, 'YYYY-MM-DD HH24:MI') AS last_autovacuum,
29
+ to_char(pg_class.reltuples, '9G999G999G999') AS rowcount,
30
+ to_char(psut.n_dead_tup, '9G999G999G999') AS dead_rowcount,
31
+ to_char(autovacuum_vacuum_threshold
32
+ + (autovacuum_vacuum_scale_factor::numeric * pg_class.reltuples), '9G999G999G999') AS autovacuum_threshold,
33
+ CASE
34
+ WHEN autovacuum_vacuum_threshold + (autovacuum_vacuum_scale_factor::numeric * pg_class.reltuples) < psut.n_dead_tup
35
+ THEN 'yes'
36
+ END AS expect_autovacuum
37
+ FROM
38
+ pg_stat_user_tables psut INNER JOIN pg_class ON psut.relid = pg_class.oid
39
+ INNER JOIN vacuum_settings ON pg_class.oid = vacuum_settings.oid
40
+ ORDER BY 1;
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-pg-extras'
4
+
5
+ namespace :pg_extras do
6
+ RubyPGExtras::QUERIES.each do |query_name|
7
+ desc RubyPGExtras.description_for(query_name: query_name)
8
+ task query_name.to_sym => :environment do
9
+ RubyPGExtras.public_send(query_name)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module RubyPGExtras
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ruby-pg-extras/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "ruby-pg-extras"
8
+ gem.version = RubyPGExtras::VERSION
9
+ gem.authors = ["pawurb"]
10
+ gem.email = ["contact@pawelurbanek.com"]
11
+ gem.summary = %q{ Ruby PostgreSQL performance database insights }
12
+ gem.description = %q{ Ruby 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
+ gem.homepage = "http://github.com/pawurb/ruby-pg-extras"
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = gem.files.grep(%r{^(spec)/})
16
+ gem.require_paths = ["lib"]
17
+ gem.license = "MIT"
18
+ gem.add_dependency "pg"
19
+ gem.add_dependency "terminal-table"
20
+ gem.add_development_dependency "rake"
21
+ gem.add_development_dependency "rspec"
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RubyPGExtras do
6
+ RubyPGExtras::QUERIES.each do |query_name|
7
+ it "#{query_name} description can be read" do
8
+ expect do
9
+ RubyPGExtras.description_for(
10
+ query_name: query_name
11
+ )
12
+ end.not_to raise_error
13
+ end
14
+ end
15
+
16
+ PG_STATS_DEPENDENT_QUERIES = %i(calls outliers)
17
+
18
+ (RubyPGExtras::QUERIES - PG_STATS_DEPENDENT_QUERIES).each do |query_name|
19
+ it "#{query_name} query can be executed" do
20
+ expect do
21
+ RubyPGExtras.run_query(
22
+ query_name: query_name,
23
+ in_format: :hash
24
+ )
25
+ end.not_to raise_error
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require_relative '../lib/ruby-pg-extras'
6
+
7
+ ENV["DATABASE_URL"] ||= "postgresql://postgres:secret@localhost:5432/ruby-pg-extras-test"
8
+
9
+ RSpec.configure do |config|
10
+ config.before :suite do
11
+ ActiveRecord::Base.establish_connection(
12
+ ENV.fetch("DATABASE_URL")
13
+ )
14
+ end
15
+
16
+ config.after :suite do
17
+ ActiveRecord::Base.remove_connection
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-pg-extras
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - pawurb
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-01-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pg
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: terminal-table
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: " Ruby port of Heroku PG Extras. The goal of this project is to provide
70
+ a powerful insights into PostgreSQL database for Ruby on Rails apps that are not
71
+ using the default Heroku PostgreSQL plugin. "
72
+ email:
73
+ - contact@pawelurbanek.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - lib/ruby-pg-extras.rb
84
+ - lib/ruby-pg-extras/queries/all_locks.sql
85
+ - lib/ruby-pg-extras/queries/bloat.sql
86
+ - lib/ruby-pg-extras/queries/blocking.sql
87
+ - lib/ruby-pg-extras/queries/cache_hit.sql
88
+ - lib/ruby-pg-extras/queries/calls.sql
89
+ - lib/ruby-pg-extras/queries/extensions.sql
90
+ - lib/ruby-pg-extras/queries/index_size.sql
91
+ - lib/ruby-pg-extras/queries/index_usage.sql
92
+ - lib/ruby-pg-extras/queries/locks.sql
93
+ - lib/ruby-pg-extras/queries/long_running_queries.sql
94
+ - lib/ruby-pg-extras/queries/mandelbrot.sql
95
+ - lib/ruby-pg-extras/queries/outliers.sql
96
+ - lib/ruby-pg-extras/queries/records_rank.sql
97
+ - lib/ruby-pg-extras/queries/seq_scans.sql
98
+ - lib/ruby-pg-extras/queries/table_indexes_size.sql
99
+ - lib/ruby-pg-extras/queries/table_size.sql
100
+ - lib/ruby-pg-extras/queries/total_index_size.sql
101
+ - lib/ruby-pg-extras/queries/total_table_size.sql
102
+ - lib/ruby-pg-extras/queries/unused_indexes.sql
103
+ - lib/ruby-pg-extras/queries/vacuum_stats.sql
104
+ - lib/ruby-pg-extras/tasks/all.rake
105
+ - lib/ruby-pg-extras/version.rb
106
+ - ruby-pg-extras.gemspec
107
+ - spec/smoke_spec.rb
108
+ - spec/spec_helper.rb
109
+ homepage: http://github.com/pawurb/ruby-pg-extras
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubygems_version: 3.0.6
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Ruby PostgreSQL performance database insights
132
+ test_files:
133
+ - spec/smoke_spec.rb
134
+ - spec/spec_helper.rb