ruby-pg-extras 2.3.0 → 3.2.1
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/.circleci/config.yml +2 -1
- data/README.md +21 -3
- data/lib/ruby-pg-extras/diagnose_data.rb +228 -0
- data/lib/ruby-pg-extras/diagnose_print.rb +46 -0
- data/lib/ruby-pg-extras/index_info.rb +61 -0
- data/lib/ruby-pg-extras/index_info_print.rb +43 -0
- data/lib/ruby-pg-extras/queries/add_extensions.sql +5 -0
- data/lib/ruby-pg-extras/queries/index_scans.sql +12 -0
- data/lib/ruby-pg-extras/queries/indexes.sql +9 -0
- data/lib/ruby-pg-extras/queries/ssl_used.sql +0 -1
- data/lib/ruby-pg-extras/queries/table_index_scans.sql +7 -0
- data/lib/ruby-pg-extras/queries/tables.sql +3 -0
- data/lib/ruby-pg-extras/queries/unused_indexes.sql +1 -1
- data/lib/ruby-pg-extras/table_info.rb +77 -0
- data/lib/ruby-pg-extras/table_info_print.rb +45 -0
- data/lib/ruby-pg-extras/version.rb +1 -1
- data/lib/ruby-pg-extras.rb +53 -5
- data/ruby-pg-extras-diagnose.png +0 -0
- data/ruby-pg-extras.gemspec +1 -0
- data/spec/diagnose_data_spec.rb +65 -0
- data/spec/diagnose_print_spec.rb +37 -0
- data/spec/index_info_spec.rb +56 -0
- data/spec/smoke_spec.rb +0 -5
- data/spec/spec_helper.rb +8 -0
- data/spec/table_info_spec.rb +78 -0
- metadata +36 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05d2e5841e02ca2a6ea167296d565a0df5e9a516c9e975589db3bd1b7856a5f7
|
4
|
+
data.tar.gz: 21a2d1b015d0558f4875f5cf1862b5e63a6e1ecbface59d88439712894716d17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0bf1ddceb1d24b718b979abc9dc03c04b94d6500487318e32805c8885f8a244bb548dd7c9dd7f738be7de0fa73f1212493a3a22c431ace69478aed2a38901dc7
|
7
|
+
data.tar.gz: 628eeea090e2839bc38d512a801f13ab7ef7413eb2c45719171dd9946800e9a98444f81a96d14102da749de85776931a616aee5e06f46a4d4f0469cca0e6cb1c
|
data/.circleci/config.yml
CHANGED
@@ -31,7 +31,8 @@ jobs:
|
|
31
31
|
- checkout
|
32
32
|
- run: gem update --system
|
33
33
|
- run: gem install bundler
|
34
|
-
- run: bundle
|
34
|
+
- run: bundle config set --local path 'vendor/bundle'
|
35
|
+
- run: bundle install
|
35
36
|
- run: sudo apt-get update --allow-releaseinfo-change
|
36
37
|
- run: sudo apt install postgresql-client-11
|
37
38
|
- run: dockerize -wait tcp://postgres11:5432 -timeout 1m
|
data/README.md
CHANGED
@@ -26,7 +26,7 @@ In your Gemfile
|
|
26
26
|
gem "ruby-pg-extras"
|
27
27
|
```
|
28
28
|
|
29
|
-
|
29
|
+
`calls` and `outliers` queries require [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) extension.
|
30
30
|
|
31
31
|
You can check if it is enabled in your database by running:
|
32
32
|
|
@@ -39,6 +39,12 @@ You should see the similar line in the output:
|
|
39
39
|
| pg_stat_statements | 1.7 | 1.7 | track execution statistics of all SQL statements executed |
|
40
40
|
```
|
41
41
|
|
42
|
+
`ssl_used` requires `sslinfo` extension, and `buffercache_usage`/`buffercache_usage` queries need `pg_buffercache`. You can enable them all by running:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
RubyPGExtras.add_extensions
|
46
|
+
```
|
47
|
+
|
42
48
|
## Usage
|
43
49
|
|
44
50
|
Gem expects the `ENV['DATABASE_URL']` value in the following format:
|
@@ -92,6 +98,18 @@ RubyPGExtras.long_running_queries(args: { threshold: "200 milliseconds" })
|
|
92
98
|
|
93
99
|
```
|
94
100
|
|
101
|
+
## Diagnose report
|
102
|
+
|
103
|
+
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:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
RubyPGExtras.diagnose
|
107
|
+
```
|
108
|
+
|
109
|
+

|
110
|
+
|
111
|
+
Keep reading to learn about methods that `diagnose` uses under the hood.
|
112
|
+
|
95
113
|
## Available methods
|
96
114
|
|
97
115
|
### `cache_hit`
|
@@ -236,7 +254,7 @@ This command displays all the current locks, regardless of their type.
|
|
236
254
|
|
237
255
|
RubyPGExtras.outliers(args: { limit: 20 })
|
238
256
|
|
239
|
-
|
257
|
+
query | exec_time | prop_exec_time | ncalls | sync_io_time
|
240
258
|
-----------------------------------------+------------------+----------------+-------------+--------------
|
241
259
|
SELECT * FROM archivable_usage_events.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00
|
242
260
|
COPY public.archivable_usage_events (.. | 50:38:33.198418 | 23.6% | 13 | 13:34:21.00108
|
@@ -387,7 +405,7 @@ This command displays the total size of each table and materialized view in the
|
|
387
405
|
|
388
406
|
```ruby
|
389
407
|
|
390
|
-
RubyPGExtras.unused_indexes(args: {
|
408
|
+
RubyPGExtras.unused_indexes(args: { max_scans: 20 })
|
391
409
|
|
392
410
|
table | index | index_size | index_scans
|
393
411
|
---------------------+--------------------------------------------+------------+-------------
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'filesize'
|
4
|
+
|
5
|
+
module RubyPGExtras
|
6
|
+
class DiagnoseData
|
7
|
+
PG_EXTRAS_TABLE_CACHE_HIT_MIN_EXPECTED = "0.985"
|
8
|
+
PG_EXTRAS_INDEX_CACHE_HIT_MIN_EXPECTED = "0.985"
|
9
|
+
PG_EXTRAS_UNUSED_INDEXES_MAX_SCANS = 20
|
10
|
+
PG_EXTRAS_UNUSED_INDEXES_MIN_SIZE_BYTES = Filesize.from("1 MB").to_i # 1000000 bytes
|
11
|
+
PG_EXTRAS_NULL_INDEXES_MIN_SIZE_MB = 1 # 1 MB
|
12
|
+
PG_EXTRAS_NULL_MIN_NULL_FRAC_PERCENT = 50 # 50%
|
13
|
+
PG_EXTRAS_BLOAT_MIN_VALUE = 10
|
14
|
+
PG_EXTRAS_OUTLIERS_MIN_EXEC_RATIO = 33 # 33%
|
15
|
+
|
16
|
+
def self.call
|
17
|
+
new.call
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
[
|
22
|
+
:table_cache_hit,
|
23
|
+
:index_cache_hit,
|
24
|
+
:unused_indexes,
|
25
|
+
:null_indexes,
|
26
|
+
:bloat,
|
27
|
+
:duplicate_indexes
|
28
|
+
].yield_self do |checks|
|
29
|
+
extensions_data = query_module.extensions(in_format: :hash)
|
30
|
+
pg_stats_enabled = extensions_data.find do |el|
|
31
|
+
el.fetch("name") == "pg_stat_statements"
|
32
|
+
end.fetch("installed_version", false)
|
33
|
+
|
34
|
+
ssl_info_enabled = extensions_data.find do |el|
|
35
|
+
el.fetch("name") == "sslinfo"
|
36
|
+
end.fetch("installed_version", false)
|
37
|
+
|
38
|
+
if pg_stats_enabled
|
39
|
+
checks = checks.concat([:outliers])
|
40
|
+
end
|
41
|
+
|
42
|
+
if ssl_info_enabled
|
43
|
+
checks = checks.concat([:ssl_used])
|
44
|
+
end
|
45
|
+
|
46
|
+
checks
|
47
|
+
end.map do |check|
|
48
|
+
send(check).merge(check_name: check)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def query_module
|
55
|
+
RubyPGExtras
|
56
|
+
end
|
57
|
+
|
58
|
+
def table_cache_hit
|
59
|
+
min_expected = ENV.fetch(
|
60
|
+
"PG_EXTRAS_TABLE_CACHE_HIT_MIN_EXPECTED",
|
61
|
+
PG_EXTRAS_TABLE_CACHE_HIT_MIN_EXPECTED
|
62
|
+
).to_f
|
63
|
+
|
64
|
+
table_cache_hit_ratio = query_module.cache_hit(in_format: :hash)[1].fetch("ratio").to_f.round(6)
|
65
|
+
|
66
|
+
if table_cache_hit_ratio > min_expected
|
67
|
+
{
|
68
|
+
ok: true,
|
69
|
+
message: "Table cache hit ratio is correct: #{table_cache_hit_ratio}."
|
70
|
+
}
|
71
|
+
else
|
72
|
+
{
|
73
|
+
ok: false,
|
74
|
+
message: "Table hit ratio is too low: #{table_cache_hit_ratio}."
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def index_cache_hit
|
80
|
+
min_expected = ENV.fetch(
|
81
|
+
"PG_EXTRAS_INDEX_CACHE_HIT_MIN_EXPECTED",
|
82
|
+
PG_EXTRAS_INDEX_CACHE_HIT_MIN_EXPECTED
|
83
|
+
).to_f
|
84
|
+
|
85
|
+
index_cache_hit_ratio = query_module.cache_hit(in_format: :hash)[0].fetch("ratio").to_f.round(6)
|
86
|
+
|
87
|
+
if index_cache_hit_ratio > min_expected
|
88
|
+
{
|
89
|
+
ok: true,
|
90
|
+
message: "Index hit ratio is correct: #{index_cache_hit_ratio}."
|
91
|
+
}
|
92
|
+
else
|
93
|
+
{
|
94
|
+
ok: false,
|
95
|
+
message: "Index hit ratio is too low: #{index_cache_hit_ratio}."
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def ssl_used
|
101
|
+
ssl_connection = query_module.ssl_used(in_format: :hash)[0].fetch("ssl_is_used")
|
102
|
+
|
103
|
+
if ssl_connection
|
104
|
+
{
|
105
|
+
ok: true,
|
106
|
+
message: "Database client is using a secure SSL connection."
|
107
|
+
}
|
108
|
+
else
|
109
|
+
{
|
110
|
+
ok: false,
|
111
|
+
message: "Database client is using an unencrypted connection."
|
112
|
+
}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def unused_indexes
|
117
|
+
indexes = query_module.unused_indexes(
|
118
|
+
in_format: :hash,
|
119
|
+
args: { min_scans: PG_EXTRAS_UNUSED_INDEXES_MAX_SCANS }
|
120
|
+
).select do |i|
|
121
|
+
Filesize.from(i.fetch("index_size")).to_i >= PG_EXTRAS_UNUSED_INDEXES_MIN_SIZE_BYTES
|
122
|
+
end
|
123
|
+
|
124
|
+
if indexes.count == 0
|
125
|
+
{
|
126
|
+
ok: true,
|
127
|
+
message: "No unused indexes detected."
|
128
|
+
}
|
129
|
+
else
|
130
|
+
print_indexes = indexes.map do |i|
|
131
|
+
"'#{i.fetch('index')}' on '#{i.fetch('table')}' size #{i.fetch('index_size')}"
|
132
|
+
end.join(",\n")
|
133
|
+
{
|
134
|
+
ok: false,
|
135
|
+
message: "Unused indexes detected:\n#{print_indexes}"
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def null_indexes
|
141
|
+
indexes = query_module.null_indexes(
|
142
|
+
in_format: :hash,
|
143
|
+
args: { min_relation_size_mb: PG_EXTRAS_NULL_INDEXES_MIN_SIZE_MB }
|
144
|
+
).select do |i|
|
145
|
+
i.fetch("null_frac").gsub("%", "").to_f >= PG_EXTRAS_NULL_MIN_NULL_FRAC_PERCENT
|
146
|
+
end
|
147
|
+
|
148
|
+
if indexes.count == 0
|
149
|
+
{
|
150
|
+
ok: true,
|
151
|
+
message: "No null indexes detected."
|
152
|
+
}
|
153
|
+
else
|
154
|
+
print_indexes = indexes.map do |i|
|
155
|
+
"'#{i.fetch('index')}' size #{i.fetch('index_size')} null values fraction #{i.fetch('null_frac')}"
|
156
|
+
end.join(",\n")
|
157
|
+
{
|
158
|
+
ok: false,
|
159
|
+
message: "Null indexes detected:\n#{print_indexes}"
|
160
|
+
}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def bloat
|
165
|
+
bloat_data = query_module.bloat(in_format: :hash).select do |b|
|
166
|
+
b.fetch("bloat").to_f >= PG_EXTRAS_BLOAT_MIN_VALUE
|
167
|
+
end
|
168
|
+
|
169
|
+
if bloat_data.count == 0
|
170
|
+
{
|
171
|
+
ok: true,
|
172
|
+
message: "No bloat detected."
|
173
|
+
}
|
174
|
+
else
|
175
|
+
print_bloat = bloat_data.map do |b|
|
176
|
+
"'#{b.fetch('object_name')}' bloat #{b.fetch('bloat')} waste #{b.fetch('waste')}"
|
177
|
+
end.join(",\n")
|
178
|
+
|
179
|
+
{
|
180
|
+
ok: false,
|
181
|
+
message: "Bloat detected:\n#{print_bloat}"
|
182
|
+
}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def duplicate_indexes
|
187
|
+
indexes = query_module.duplicate_indexes(in_format: :hash)
|
188
|
+
|
189
|
+
if indexes.count == 0
|
190
|
+
{
|
191
|
+
ok: true,
|
192
|
+
message: "No duplicate indexes detected."
|
193
|
+
}
|
194
|
+
else
|
195
|
+
print_indexes = indexes.map do |i|
|
196
|
+
"'#{i.fetch('idx1')}' of size #{i.fetch('size')} is identical to '#{i.fetch('idx2')}'"
|
197
|
+
end.join(",\n")
|
198
|
+
|
199
|
+
{
|
200
|
+
ok: false,
|
201
|
+
message: "Duplicate indexes detected:\n#{print_indexes}"
|
202
|
+
}
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def outliers
|
207
|
+
queries = query_module.outliers(in_format: :hash).select do |q|
|
208
|
+
q.fetch("prop_exec_time").gsub("%", "").to_f >= PG_EXTRAS_OUTLIERS_MIN_EXEC_RATIO
|
209
|
+
end
|
210
|
+
|
211
|
+
if queries.count == 0
|
212
|
+
{
|
213
|
+
ok: true,
|
214
|
+
message: "No queries using significant execution ratio detected."
|
215
|
+
}
|
216
|
+
else
|
217
|
+
print_queries = queries.map do |q|
|
218
|
+
"'#{q.fetch('query').slice(0, 30)}...' called #{q.fetch('ncalls')} times, using #{q.fetch('prop_exec_time')} of total exec time."
|
219
|
+
end.join(",\n")
|
220
|
+
|
221
|
+
{
|
222
|
+
ok: false,
|
223
|
+
message: "Queries using significant execution ratio detected:\n#{print_queries}"
|
224
|
+
}
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'terminal-table'
|
4
|
+
|
5
|
+
module RubyPGExtras
|
6
|
+
class DiagnosePrint
|
7
|
+
def self.call(data)
|
8
|
+
new.call(data)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(data)
|
12
|
+
rows = data.sort do |el|
|
13
|
+
el.fetch(:ok) ? 1 : -1
|
14
|
+
end.map do |el|
|
15
|
+
symbol = el.fetch(:ok) ? "√" : "x"
|
16
|
+
color = el.fetch(:ok) ? :green : :red
|
17
|
+
|
18
|
+
[
|
19
|
+
colorize("[#{symbol}] - #{el.fetch(:check_name)}", color),
|
20
|
+
colorize(el.fetch(:message), color)
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
puts Terminal::Table.new(
|
25
|
+
title: title,
|
26
|
+
rows: rows
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def title
|
33
|
+
"ruby-pg-extras - diagnose report"
|
34
|
+
end
|
35
|
+
|
36
|
+
def colorize(string, color)
|
37
|
+
if color == :red
|
38
|
+
"\e[0;31;49m#{string}\e[0m"
|
39
|
+
elsif color == :green
|
40
|
+
"\e[0;32;49m#{string}\e[0m"
|
41
|
+
else
|
42
|
+
raise "Unsupported color: #{color}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RubyPGExtras
|
2
|
+
class IndexInfo
|
3
|
+
def self.call(table_name = nil)
|
4
|
+
new.call(table_name)
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(table_name = nil)
|
8
|
+
indexes_data.select do |index_data|
|
9
|
+
if table_name == nil
|
10
|
+
true
|
11
|
+
else
|
12
|
+
index_data.fetch("tablename") == table_name
|
13
|
+
end
|
14
|
+
end.sort_by do |index_data|
|
15
|
+
index_data.fetch("tablename")
|
16
|
+
end.map do |index_data|
|
17
|
+
index_name = index_data.fetch("indexname")
|
18
|
+
|
19
|
+
{
|
20
|
+
index_name: index_name,
|
21
|
+
table_name: index_data.fetch("tablename"),
|
22
|
+
columns: index_data.fetch("columns").split(',').map(&:strip),
|
23
|
+
index_size: index_size_data.find do |el|
|
24
|
+
el.fetch("name") == index_name
|
25
|
+
end.fetch("size", "N/A"),
|
26
|
+
index_scans: index_scans_data.find do |el|
|
27
|
+
el.fetch("index") == index_name
|
28
|
+
end.fetch("index_scans", "N/A"),
|
29
|
+
null_frac: null_indexes_data.find do |el|
|
30
|
+
el.fetch("index") == index_name
|
31
|
+
end&.fetch("null_frac", "N/A") || "0.00%"
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def index_size_data
|
37
|
+
@_index_size_data ||= query_module.index_size(in_format: :hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
def null_indexes_data
|
41
|
+
@_null_indexes_data ||= query_module.null_indexes(
|
42
|
+
in_format: :hash,
|
43
|
+
args: { min_relation_size_mb: 0 }
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def index_scans_data
|
48
|
+
@_index_scans_data ||= query_module.index_scans(in_format: :hash)
|
49
|
+
end
|
50
|
+
|
51
|
+
def indexes_data
|
52
|
+
@_indexes_data ||= query_module.indexes(in_format: :hash)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def query_module
|
58
|
+
RubyPGExtras
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'terminal-table'
|
4
|
+
|
5
|
+
module RubyPGExtras
|
6
|
+
class IndexInfoPrint
|
7
|
+
def self.call(data)
|
8
|
+
new.call(data)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(data)
|
12
|
+
rows = data.map do |el|
|
13
|
+
[
|
14
|
+
el.fetch(:index_name),
|
15
|
+
el.fetch(:table_name),
|
16
|
+
el.fetch(:columns).join(', '),
|
17
|
+
el.fetch(:index_size),
|
18
|
+
el.fetch(:index_scans),
|
19
|
+
el.fetch(:null_frac)
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
puts Terminal::Table.new(
|
24
|
+
headings: [
|
25
|
+
"Index name",
|
26
|
+
"Table name",
|
27
|
+
"Columns",
|
28
|
+
"Index size",
|
29
|
+
"Index scans",
|
30
|
+
"Null frac"
|
31
|
+
],
|
32
|
+
title: title,
|
33
|
+
rows: rows
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def title
|
40
|
+
"Index info"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/* Number of scans performed on indexes */
|
2
|
+
|
3
|
+
SELECT
|
4
|
+
schemaname,
|
5
|
+
relname AS table,
|
6
|
+
indexrelname AS index,
|
7
|
+
pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
|
8
|
+
idx_scan as index_scans
|
9
|
+
FROM pg_stat_user_indexes ui
|
10
|
+
JOIN pg_index i ON ui.indexrelid = i.indexrelid
|
11
|
+
ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
|
12
|
+
pg_relation_size(i.indexrelid) DESC;
|
@@ -11,6 +11,6 @@ SELECT
|
|
11
11
|
idx_scan as index_scans
|
12
12
|
FROM pg_stat_user_indexes ui
|
13
13
|
JOIN pg_index i ON ui.indexrelid = i.indexrelid
|
14
|
-
WHERE NOT indisunique AND idx_scan < %{
|
14
|
+
WHERE NOT indisunique AND idx_scan < %{max_scans} AND pg_relation_size(relid) > 5 * 8192
|
15
15
|
ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
|
16
16
|
pg_relation_size(i.indexrelid) DESC;
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module RubyPGExtras
|
2
|
+
class TableInfo
|
3
|
+
def self.call(table_name = nil)
|
4
|
+
new.call(table_name)
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(table_name)
|
8
|
+
tables_data.select do |table_data|
|
9
|
+
if table_name == nil
|
10
|
+
true
|
11
|
+
else
|
12
|
+
table_data.fetch("tablename") == table_name
|
13
|
+
end
|
14
|
+
end.sort_by do |table_data|
|
15
|
+
table_data.fetch("tablename")
|
16
|
+
end.map do |table_data|
|
17
|
+
table_name = table_data.fetch("tablename")
|
18
|
+
|
19
|
+
{
|
20
|
+
table_name: table_name,
|
21
|
+
table_size: table_size_data.find do |el|
|
22
|
+
el.fetch("name") == table_name
|
23
|
+
end.fetch("size", "N/A"),
|
24
|
+
table_cache_hit: table_cache_hit_data.find do |el|
|
25
|
+
el.fetch("name") == table_name
|
26
|
+
end.fetch("ratio", "N/A"),
|
27
|
+
indexes_cache_hit: index_cache_hit_data.find do |el|
|
28
|
+
el.fetch("name") == table_name
|
29
|
+
end.fetch("ratio", "N/A"),
|
30
|
+
estimated_rows: records_rank_data.find do |el|
|
31
|
+
el.fetch("name") == table_name
|
32
|
+
end.fetch("estimated_count", "N/A"),
|
33
|
+
sequential_scans: seq_scans_data.find do |el|
|
34
|
+
el.fetch("name") == table_name
|
35
|
+
end.fetch("count", "N/A"),
|
36
|
+
indexes_scans: table_index_scans_data.find do |el|
|
37
|
+
el.fetch("name") == table_name
|
38
|
+
end.fetch("count", "N/A")
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def index_cache_hit_data
|
46
|
+
@_index_cache_hit_data ||= query_module.index_cache_hit(in_format: :hash)
|
47
|
+
end
|
48
|
+
|
49
|
+
def table_cache_hit_data
|
50
|
+
@_table_cache_hit_data ||= query_module.table_cache_hit(in_format: :hash)
|
51
|
+
end
|
52
|
+
|
53
|
+
def table_size_data
|
54
|
+
@_table_size_data ||= query_module.table_size(in_format: :hash)
|
55
|
+
end
|
56
|
+
|
57
|
+
def records_rank_data
|
58
|
+
@_records_rank_data ||= query_module.records_rank(in_format: :hash)
|
59
|
+
end
|
60
|
+
|
61
|
+
def tables_data
|
62
|
+
@_tables_data ||= query_module.tables(in_format: :hash)
|
63
|
+
end
|
64
|
+
|
65
|
+
def seq_scans_data
|
66
|
+
@_seq_scans_data ||= query_module.seq_scans(in_format: :hash)
|
67
|
+
end
|
68
|
+
|
69
|
+
def table_index_scans_data
|
70
|
+
@_table_index_scans_data ||= query_module.table_index_scans(in_format: :hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
def query_module
|
74
|
+
RubyPGExtras
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'terminal-table'
|
4
|
+
|
5
|
+
module RubyPGExtras
|
6
|
+
class TableInfoPrint
|
7
|
+
def self.call(data)
|
8
|
+
new.call(data)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(data)
|
12
|
+
rows = data.map do |el|
|
13
|
+
[
|
14
|
+
el.fetch(:table_name),
|
15
|
+
el.fetch(:table_size),
|
16
|
+
el.fetch(:table_cache_hit),
|
17
|
+
el.fetch(:indexes_cache_hit),
|
18
|
+
el.fetch(:estimated_rows),
|
19
|
+
el.fetch(:sequential_scans),
|
20
|
+
el.fetch(:indexes_scans)
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
puts Terminal::Table.new(
|
25
|
+
headings: [
|
26
|
+
"Table name",
|
27
|
+
"Table size",
|
28
|
+
"Table cache hit",
|
29
|
+
"Indexes cache hit",
|
30
|
+
"Estimated rows",
|
31
|
+
"Sequentail scans",
|
32
|
+
"Indexes scans"
|
33
|
+
],
|
34
|
+
title: title,
|
35
|
+
rows: rows
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def title
|
42
|
+
"Table info"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/ruby-pg-extras.rb
CHANGED
@@ -3,17 +3,23 @@
|
|
3
3
|
require 'terminal-table'
|
4
4
|
require 'uri'
|
5
5
|
require 'pg'
|
6
|
+
require 'ruby-pg-extras/diagnose_data'
|
7
|
+
require 'ruby-pg-extras/diagnose_print'
|
8
|
+
require 'ruby-pg-extras/index_info'
|
9
|
+
require 'ruby-pg-extras/index_info_print'
|
10
|
+
require 'ruby-pg-extras/table_info'
|
11
|
+
require 'ruby-pg-extras/table_info_print'
|
6
12
|
|
7
13
|
module RubyPGExtras
|
8
14
|
@@database_url = nil
|
9
15
|
NEW_PG_STAT_STATEMENTS = "1.8"
|
10
16
|
|
11
17
|
QUERIES = %i(
|
12
|
-
bloat blocking cache_hit db_settings
|
13
|
-
calls extensions table_cache_hit index_cache_hit
|
14
|
-
index_size index_usage null_indexes locks all_locks
|
18
|
+
add_extensions bloat blocking cache_hit db_settings
|
19
|
+
calls extensions table_cache_hit tables index_cache_hit
|
20
|
+
indexes index_size index_usage index_scans null_indexes locks all_locks
|
15
21
|
long_running_queries mandelbrot outliers
|
16
|
-
records_rank seq_scans table_indexes_size
|
22
|
+
records_rank seq_scans table_index_scans table_indexes_size
|
17
23
|
table_size total_index_size total_table_size
|
18
24
|
unused_indexes duplicate_indexes vacuum_stats kill_all
|
19
25
|
pg_stat_statements_reset buffercache_stats
|
@@ -28,7 +34,7 @@ module RubyPGExtras
|
|
28
34
|
outliers_legacy: { limit: 10 },
|
29
35
|
buffercache_stats: { limit: 10 },
|
30
36
|
buffercache_usage: { limit: 20 },
|
31
|
-
unused_indexes: {
|
37
|
+
unused_indexes: { max_scans: 50 },
|
32
38
|
null_indexes: { min_relation_size_mb: 10 }
|
33
39
|
})
|
34
40
|
|
@@ -67,6 +73,48 @@ module RubyPGExtras
|
|
67
73
|
)
|
68
74
|
end
|
69
75
|
|
76
|
+
def self.diagnose(in_format: :display_table)
|
77
|
+
data = RubyPGExtras::DiagnoseData.call
|
78
|
+
|
79
|
+
if in_format == :display_table
|
80
|
+
RubyPGExtras::DiagnosePrint.call(data)
|
81
|
+
elsif in_format == :hash
|
82
|
+
data
|
83
|
+
elsif in_format == :array
|
84
|
+
data.map(&:values)
|
85
|
+
else
|
86
|
+
raise "Invalid 'in_format' argument!"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.index_info(args: {}, in_format: :display_table)
|
91
|
+
data = RubyPGExtras::IndexInfo.call(args[:table_name])
|
92
|
+
|
93
|
+
if in_format == :display_table
|
94
|
+
RubyPGExtras::IndexInfoPrint.call(data)
|
95
|
+
elsif in_format == :hash
|
96
|
+
data
|
97
|
+
elsif in_format == :array
|
98
|
+
data.map(&:values)
|
99
|
+
else
|
100
|
+
raise "Invalid 'in_format' argument!"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.table_info(args: {}, in_format: :display_table)
|
105
|
+
data = RubyPGExtras::TableInfo.call(args[:table_name])
|
106
|
+
|
107
|
+
if in_format == :display_table
|
108
|
+
RubyPGExtras::TableInfoPrint.call(data)
|
109
|
+
elsif in_format == :hash
|
110
|
+
data
|
111
|
+
elsif in_format == :array
|
112
|
+
data.map(&:values)
|
113
|
+
else
|
114
|
+
raise "Invalid 'in_format' argument!"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
70
118
|
def self.display_result(result, title:, in_format:)
|
71
119
|
case in_format
|
72
120
|
when :array
|
Binary file
|
data/ruby-pg-extras.gemspec
CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.require_paths = ["lib"]
|
17
17
|
gem.license = "MIT"
|
18
18
|
gem.add_dependency "pg"
|
19
|
+
gem.add_dependency "filesize"
|
19
20
|
gem.add_dependency "terminal-table"
|
20
21
|
gem.add_development_dependency "rake"
|
21
22
|
gem.add_development_dependency "rspec"
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe RubyPGExtras::DiagnoseData do
|
6
|
+
subject(:result) do
|
7
|
+
RubyPGExtras::DiagnoseData.call
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "call" do
|
11
|
+
context "stubbed cases" do
|
12
|
+
before do
|
13
|
+
expect(RubyPGExtras).to receive(:unused_indexes) {
|
14
|
+
[
|
15
|
+
{ "table" => "public.plans", "index" => "index_plans_on_payer_id", "index_size" => "16 MB", "index_scans" => 0 },
|
16
|
+
{ "table" => "public.feedbacks", "index" => "index_feedbacks_on_target_id", "index_size" => "80 kB", "index_scans" => 1 },
|
17
|
+
{ "table" => "public.channels", "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "index_scans" => 7}
|
18
|
+
]
|
19
|
+
}
|
20
|
+
|
21
|
+
expect(RubyPGExtras).to receive(:null_indexes) {
|
22
|
+
[
|
23
|
+
{ "oid" => 123, "index" => "index_plans_on_payer_id", "index_size" => "16 MB", "unique" => true, "null_frac" => "00.00%", "expected_saving" => "0 kb" },
|
24
|
+
{ "oid" => 321, "index" => "index_feedbacks_on_target_id", "index_size" => "80 kB", "unique" => true, "null_frac" => "97.00%", "expected_saving" => "77 kb" },
|
25
|
+
{ "oid" => 231, "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "unique" => true, "null_frac" => "49.99%", "expected_saving" => "28 MB" }
|
26
|
+
]
|
27
|
+
}
|
28
|
+
|
29
|
+
expect(RubyPGExtras).to receive(:bloat) {
|
30
|
+
[
|
31
|
+
{ "type" => "table", "schemaname" => "public", "object_name" => "bloated_table_1", "bloat" => 8, "waste" => "0 kb" },
|
32
|
+
{ "type" => "table", "schemaname" => "public", "object_name" => "bloated_table_2", "bloat" => 8, "waste" => "77 kb" },
|
33
|
+
{ "type" => "schemaname", "public" => "index_channels_on_slack_id", "object_name" => "bloated_index", "bloat" => 11, "waste" => "28 MB" }
|
34
|
+
]
|
35
|
+
}
|
36
|
+
|
37
|
+
expect(RubyPGExtras).to receive(:duplicate_indexes) {
|
38
|
+
[
|
39
|
+
{ "size" => "128 kb", "idx1" => "users_pkey", "idx2" => "index_users_id" }
|
40
|
+
]
|
41
|
+
}
|
42
|
+
|
43
|
+
expect(RubyPGExtras).to receive(:outliers) {
|
44
|
+
[
|
45
|
+
{ "query" => "SELECT * FROM users WHERE users.age > 20 AND users.height > 160", "exec_time" => "154:39:26.431466", "prop_exec_time" => "72.2%", "ncalls" => "34,211,877", "sync_io_time" => "00:34:19.784318" }
|
46
|
+
]
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
it "works" do
|
51
|
+
expect {
|
52
|
+
RubyPGExtras::DiagnosePrint.call(result)
|
53
|
+
}.not_to raise_error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "real database data" do
|
58
|
+
it "works" do
|
59
|
+
expect {
|
60
|
+
RubyPGExtras::DiagnosePrint.call(result)
|
61
|
+
}.not_to raise_error
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe RubyPGExtras::DiagnosePrint do
|
6
|
+
subject(:print_result) do
|
7
|
+
RubyPGExtras::DiagnosePrint.call(data)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:data) do
|
11
|
+
[
|
12
|
+
{
|
13
|
+
:check_name => :table_cache_hit,
|
14
|
+
:ok => false,
|
15
|
+
:message => "Table hit ratio too low: 0.906977."
|
16
|
+
},
|
17
|
+
{
|
18
|
+
:check_name => :index_cache_hit,
|
19
|
+
:ok => false,
|
20
|
+
:message => "Index hit ratio is too low: 0.818182."
|
21
|
+
},
|
22
|
+
{
|
23
|
+
:check_name => :ssl_used,
|
24
|
+
:ok => true,
|
25
|
+
:message => "Database client is using a secure SSL connection."
|
26
|
+
}
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "call" do
|
31
|
+
it "works" do
|
32
|
+
expect {
|
33
|
+
print_result
|
34
|
+
}.not_to raise_error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe RubyPGExtras::IndexInfo do
|
6
|
+
subject(:result) do
|
7
|
+
RubyPGExtras::IndexInfo.call
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "call" do
|
11
|
+
context "stubbed cases" do
|
12
|
+
before do
|
13
|
+
expect(RubyPGExtras).to receive(:indexes) {
|
14
|
+
[
|
15
|
+
{ "schemaname" => "public", "indexname" => "index_users_on_api_auth_token", "tablename" => "users", "columns" => "api_auth_token, column2" },
|
16
|
+
{"schemaname" => "public", "indexname" => "index_teams_on_slack_id", "tablename" => "teams", "columns" => "slack_id" },
|
17
|
+
]
|
18
|
+
}
|
19
|
+
|
20
|
+
expect(RubyPGExtras).to receive(:index_size) {
|
21
|
+
[
|
22
|
+
{ "name" => "index_users_on_api_auth_token", "size" => "1744 kB" },
|
23
|
+
{"name" => "index_teams_on_slack_id", "size" => "500 kB"},
|
24
|
+
]
|
25
|
+
}
|
26
|
+
|
27
|
+
expect(RubyPGExtras).to receive(:null_indexes) {
|
28
|
+
[
|
29
|
+
{ "oid" => 16803, "index" => "index_users_on_api_auth_token", "index_size" => "1744 kB", "unique"=>true, "indexed_column" => "api_auth_token", "null_frac" => "25.00%", "expected_saving" => "300 kB" }
|
30
|
+
]
|
31
|
+
}
|
32
|
+
|
33
|
+
expect(RubyPGExtras).to receive(:index_scans) {
|
34
|
+
[
|
35
|
+
{ "schemaname" => "public", "table" => "users", "index" => "index_users_on_api_auth_token", "index_size" => "1744 kB", "index_scans"=> 0 },
|
36
|
+
{ "schemaname" => "public", "table" => "teams", "index" => "index_teams_on_slack_id", "index_size" => "500 kB", "index_scans"=> 0 }
|
37
|
+
]
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
it "works" do
|
42
|
+
expect {
|
43
|
+
RubyPGExtras::IndexInfoPrint.call(result)
|
44
|
+
}.not_to raise_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "real data" do
|
49
|
+
it "works" do
|
50
|
+
expect {
|
51
|
+
RubyPGExtras::IndexInfoPrint.call(result)
|
52
|
+
}.not_to raise_error
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/smoke_spec.rb
CHANGED
@@ -3,11 +3,6 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
describe RubyPGExtras do
|
6
|
-
before(:all) do
|
7
|
-
RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS pg_buffercache;")
|
8
|
-
RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS pg_stat_statements;")
|
9
|
-
end
|
10
|
-
|
11
6
|
RubyPGExtras::QUERIES.each do |query_name|
|
12
7
|
it "#{query_name} description can be read" do
|
13
8
|
expect do
|
data/spec/spec_helper.rb
CHANGED
@@ -17,3 +17,11 @@ else
|
|
17
17
|
end
|
18
18
|
|
19
19
|
ENV["DATABASE_URL"] ||= "postgresql://postgres:secret@localhost:#{port}/ruby-pg-extras-test"
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.before(:suite) do
|
23
|
+
RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS pg_stat_statements;")
|
24
|
+
RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS pg_buffercache;")
|
25
|
+
RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS sslinfo;")
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe RubyPGExtras::TableInfo do
|
6
|
+
subject(:result) do
|
7
|
+
RubyPGExtras::TableInfo.call
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "call" do
|
11
|
+
context "stubbed cases" do
|
12
|
+
before do
|
13
|
+
expect(RubyPGExtras).to receive(:tables) {
|
14
|
+
[
|
15
|
+
{ "schemaname" => "public", "tablename" => "users" },
|
16
|
+
{ "schemaname" => "public", "tablename" => "teams" }
|
17
|
+
]
|
18
|
+
}
|
19
|
+
|
20
|
+
expect(RubyPGExtras).to receive(:table_size) {
|
21
|
+
[
|
22
|
+
{ "name" => "teams", "size" => "25 MB" },
|
23
|
+
{"name" => "users", "size" => "250 MB"},
|
24
|
+
]
|
25
|
+
}
|
26
|
+
|
27
|
+
expect(RubyPGExtras).to receive(:index_cache_hit) {
|
28
|
+
[
|
29
|
+
{ "name" => "teams", "ratio" => "0.98" },
|
30
|
+
{ "name" => "users", "ratio" => "0.999" },
|
31
|
+
]
|
32
|
+
}
|
33
|
+
|
34
|
+
expect(RubyPGExtras).to receive(:table_cache_hit) {
|
35
|
+
[
|
36
|
+
{ "name" => "teams", "ratio" => "0.88" },
|
37
|
+
{ "name" => "users", "ratio" => "0.899" },
|
38
|
+
]
|
39
|
+
}
|
40
|
+
|
41
|
+
expect(RubyPGExtras).to receive(:records_rank) {
|
42
|
+
[
|
43
|
+
{ "name" => "teams", "estimated_count" => "358" },
|
44
|
+
{ "name" => "users", "estimated_count" => "8973" },
|
45
|
+
]
|
46
|
+
}
|
47
|
+
|
48
|
+
expect(RubyPGExtras).to receive(:seq_scans) {
|
49
|
+
[
|
50
|
+
{ "name" => "teams", "count" => "0" },
|
51
|
+
{ "name" => "users", "count" => "409328" },
|
52
|
+
]
|
53
|
+
}
|
54
|
+
|
55
|
+
expect(RubyPGExtras).to receive(:table_index_scans) {
|
56
|
+
[
|
57
|
+
{ "name" => "teams", "count" => "8579" },
|
58
|
+
{ "name" => "users", "count" => "0" },
|
59
|
+
]
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
it "works" do
|
64
|
+
expect {
|
65
|
+
RubyPGExtras::TableInfoPrint.call(result)
|
66
|
+
}.not_to raise_error
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "real data" do
|
71
|
+
it "works" do
|
72
|
+
expect {
|
73
|
+
RubyPGExtras::TableInfoPrint.call(result)
|
74
|
+
}.not_to raise_error
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-pg-extras
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 3.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: filesize
|
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'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: terminal-table
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,6 +97,11 @@ files:
|
|
83
97
|
- Rakefile
|
84
98
|
- docker-compose.yml.sample
|
85
99
|
- lib/ruby-pg-extras.rb
|
100
|
+
- lib/ruby-pg-extras/diagnose_data.rb
|
101
|
+
- lib/ruby-pg-extras/diagnose_print.rb
|
102
|
+
- lib/ruby-pg-extras/index_info.rb
|
103
|
+
- lib/ruby-pg-extras/index_info_print.rb
|
104
|
+
- lib/ruby-pg-extras/queries/add_extensions.sql
|
86
105
|
- lib/ruby-pg-extras/queries/all_locks.sql
|
87
106
|
- lib/ruby-pg-extras/queries/bloat.sql
|
88
107
|
- lib/ruby-pg-extras/queries/blocking.sql
|
@@ -95,8 +114,10 @@ files:
|
|
95
114
|
- lib/ruby-pg-extras/queries/duplicate_indexes.sql
|
96
115
|
- lib/ruby-pg-extras/queries/extensions.sql
|
97
116
|
- lib/ruby-pg-extras/queries/index_cache_hit.sql
|
117
|
+
- lib/ruby-pg-extras/queries/index_scans.sql
|
98
118
|
- lib/ruby-pg-extras/queries/index_size.sql
|
99
119
|
- lib/ruby-pg-extras/queries/index_usage.sql
|
120
|
+
- lib/ruby-pg-extras/queries/indexes.sql
|
100
121
|
- lib/ruby-pg-extras/queries/kill_all.sql
|
101
122
|
- lib/ruby-pg-extras/queries/locks.sql
|
102
123
|
- lib/ruby-pg-extras/queries/long_running_queries.sql
|
@@ -109,16 +130,25 @@ files:
|
|
109
130
|
- lib/ruby-pg-extras/queries/seq_scans.sql
|
110
131
|
- lib/ruby-pg-extras/queries/ssl_used.sql
|
111
132
|
- lib/ruby-pg-extras/queries/table_cache_hit.sql
|
133
|
+
- lib/ruby-pg-extras/queries/table_index_scans.sql
|
112
134
|
- lib/ruby-pg-extras/queries/table_indexes_size.sql
|
113
135
|
- lib/ruby-pg-extras/queries/table_size.sql
|
136
|
+
- lib/ruby-pg-extras/queries/tables.sql
|
114
137
|
- lib/ruby-pg-extras/queries/total_index_size.sql
|
115
138
|
- lib/ruby-pg-extras/queries/total_table_size.sql
|
116
139
|
- lib/ruby-pg-extras/queries/unused_indexes.sql
|
117
140
|
- lib/ruby-pg-extras/queries/vacuum_stats.sql
|
141
|
+
- lib/ruby-pg-extras/table_info.rb
|
142
|
+
- lib/ruby-pg-extras/table_info_print.rb
|
118
143
|
- lib/ruby-pg-extras/version.rb
|
144
|
+
- ruby-pg-extras-diagnose.png
|
119
145
|
- ruby-pg-extras.gemspec
|
146
|
+
- spec/diagnose_data_spec.rb
|
147
|
+
- spec/diagnose_print_spec.rb
|
148
|
+
- spec/index_info_spec.rb
|
120
149
|
- spec/smoke_spec.rb
|
121
150
|
- spec/spec_helper.rb
|
151
|
+
- spec/table_info_spec.rb
|
122
152
|
homepage: http://github.com/pawurb/ruby-pg-extras
|
123
153
|
licenses:
|
124
154
|
- MIT
|
@@ -143,5 +173,9 @@ signing_key:
|
|
143
173
|
specification_version: 4
|
144
174
|
summary: Ruby PostgreSQL performance database insights
|
145
175
|
test_files:
|
176
|
+
- spec/diagnose_data_spec.rb
|
177
|
+
- spec/diagnose_print_spec.rb
|
178
|
+
- spec/index_info_spec.rb
|
146
179
|
- spec/smoke_spec.rb
|
147
180
|
- spec/spec_helper.rb
|
181
|
+
- spec/table_info_spec.rb
|