ruby-pg-extras 2.3.0 → 3.0.6
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 +230 -0
- data/lib/ruby-pg-extras/diagnose_print.rb +48 -0
- data/lib/ruby-pg-extras/queries/add_extensions.sql +5 -0
- data/lib/ruby-pg-extras/queries/ssl_used.sql +0 -1
- data/lib/ruby-pg-extras/queries/unused_indexes.sql +1 -1
- data/lib/ruby-pg-extras/version.rb +1 -1
- data/lib/ruby-pg-extras.rb +16 -2
- data/ruby-pg-extras-diagnose.png +0 -0
- data/ruby-pg-extras.gemspec +1 -0
- data/spec/diagnose_data_spec.rb +76 -0
- data/spec/diagnose_print_spec.rb +34 -0
- data/spec/smoke_spec.rb +0 -5
- data/spec/spec_helper.rb +8 -0
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed716b44ced4653bf0c794ea1690b99cecf460f30dc767a56f328748790d4c0f
|
4
|
+
data.tar.gz: 3652a5add3cfd162959578af4d840ee494f99311d4b2e48ffbd2f1ca5c078b1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3078b1e69e4a36686cc76a2c5326a35b8a61b4751cfe1031df74cda6306ae62fcd07026be2218ca40ac83dd535d0748e608bf00e5e3571aae9de0f1d794d43ff
|
7
|
+
data.tar.gz: 116e9de1f4a0dc927eef92e6bc88f354c54a5929831d68d7a19c58efbd8085329b0abff6cde1d482757c41d27225191e50bf2852acd9d57637d2306f61568fa7
|
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,230 @@
|
|
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: table_cache_hit,
|
23
|
+
index_cache_hit: index_cache_hit,
|
24
|
+
unused_indexes: unused_indexes,
|
25
|
+
null_indexes: null_indexes,
|
26
|
+
bloat: bloat,
|
27
|
+
duplicate_indexes: 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.merge!({
|
40
|
+
outliers: outliers
|
41
|
+
})
|
42
|
+
end
|
43
|
+
|
44
|
+
if ssl_info_enabled
|
45
|
+
checks.merge!({
|
46
|
+
ssl_used: ssl_used
|
47
|
+
})
|
48
|
+
end
|
49
|
+
|
50
|
+
checks
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def query_module
|
57
|
+
RubyPGExtras
|
58
|
+
end
|
59
|
+
|
60
|
+
def table_cache_hit
|
61
|
+
min_expected = ENV.fetch(
|
62
|
+
"PG_EXTRAS_TABLE_CACHE_HIT_MIN_EXPECTED",
|
63
|
+
PG_EXTRAS_TABLE_CACHE_HIT_MIN_EXPECTED
|
64
|
+
).to_f
|
65
|
+
|
66
|
+
table_cache_hit_ratio = query_module.cache_hit(in_format: :hash)[1].fetch("ratio").to_f.round(6)
|
67
|
+
|
68
|
+
if table_cache_hit_ratio > min_expected
|
69
|
+
{
|
70
|
+
ok: true,
|
71
|
+
message: "Table cache hit ratio is correct: #{table_cache_hit_ratio}."
|
72
|
+
}
|
73
|
+
else
|
74
|
+
{
|
75
|
+
ok: false,
|
76
|
+
message: "Table hit ratio is too low: #{table_cache_hit_ratio}."
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def index_cache_hit
|
82
|
+
min_expected = ENV.fetch(
|
83
|
+
"PG_EXTRAS_INDEX_CACHE_HIT_MIN_EXPECTED",
|
84
|
+
PG_EXTRAS_INDEX_CACHE_HIT_MIN_EXPECTED
|
85
|
+
).to_f
|
86
|
+
|
87
|
+
index_cache_hit_ratio = query_module.cache_hit(in_format: :hash)[0].fetch("ratio").to_f.round(6)
|
88
|
+
|
89
|
+
if index_cache_hit_ratio > min_expected
|
90
|
+
{
|
91
|
+
ok: true,
|
92
|
+
message: "Index hit ratio is correct: #{index_cache_hit_ratio}."
|
93
|
+
}
|
94
|
+
else
|
95
|
+
{
|
96
|
+
ok: false,
|
97
|
+
message: "Index hit ratio is too low: #{index_cache_hit_ratio}."
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def ssl_used
|
103
|
+
ssl_connection = query_module.ssl_used(in_format: :hash)[0].fetch("ssl_is_used")
|
104
|
+
|
105
|
+
if ssl_connection
|
106
|
+
{
|
107
|
+
ok: true,
|
108
|
+
message: "Database client is using a secure SSL connection."
|
109
|
+
}
|
110
|
+
else
|
111
|
+
{
|
112
|
+
ok: false,
|
113
|
+
message: "Database client is using an unencrypted connection."
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def unused_indexes
|
119
|
+
indexes = query_module.unused_indexes(
|
120
|
+
in_format: :hash,
|
121
|
+
args: { min_scans: PG_EXTRAS_UNUSED_INDEXES_MAX_SCANS }
|
122
|
+
).select do |i|
|
123
|
+
Filesize.from(i.fetch("index_size")).to_i >= PG_EXTRAS_UNUSED_INDEXES_MIN_SIZE_BYTES
|
124
|
+
end
|
125
|
+
|
126
|
+
if indexes.count == 0
|
127
|
+
{
|
128
|
+
ok: true,
|
129
|
+
message: "No unused indexes detected."
|
130
|
+
}
|
131
|
+
else
|
132
|
+
print_indexes = indexes.map do |i|
|
133
|
+
"'#{i.fetch('index')}' on '#{i.fetch('table')}' size #{i.fetch('index_size')}"
|
134
|
+
end.join(",\n")
|
135
|
+
{
|
136
|
+
ok: false,
|
137
|
+
message: "Unused indexes detected:\n#{print_indexes}"
|
138
|
+
}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def null_indexes
|
143
|
+
indexes = query_module.null_indexes(
|
144
|
+
in_format: :hash,
|
145
|
+
args: { min_relation_size_mb: PG_EXTRAS_NULL_INDEXES_MIN_SIZE_MB }
|
146
|
+
).select do |i|
|
147
|
+
i.fetch("null_frac").gsub("%", "").to_f >= PG_EXTRAS_NULL_MIN_NULL_FRAC_PERCENT
|
148
|
+
end
|
149
|
+
|
150
|
+
if indexes.count == 0
|
151
|
+
{
|
152
|
+
ok: true,
|
153
|
+
message: "No null indexes detected."
|
154
|
+
}
|
155
|
+
else
|
156
|
+
print_indexes = indexes.map do |i|
|
157
|
+
"'#{i.fetch('index')}' size #{i.fetch('index_size')} null values fraction #{i.fetch('null_frac')}"
|
158
|
+
end.join(",\n")
|
159
|
+
{
|
160
|
+
ok: false,
|
161
|
+
message: "Null indexes detected:\n#{print_indexes}"
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def bloat
|
167
|
+
bloat_data = query_module.bloat(in_format: :hash).select do |b|
|
168
|
+
b.fetch("bloat").to_f >= PG_EXTRAS_BLOAT_MIN_VALUE
|
169
|
+
end
|
170
|
+
|
171
|
+
if bloat_data.count == 0
|
172
|
+
{
|
173
|
+
ok: true,
|
174
|
+
message: "No bloat detected."
|
175
|
+
}
|
176
|
+
else
|
177
|
+
print_bloat = bloat_data.map do |b|
|
178
|
+
"'#{b.fetch('object_name')}' bloat #{b.fetch('bloat')} waste #{b.fetch('waste')}"
|
179
|
+
end.join(",\n")
|
180
|
+
|
181
|
+
{
|
182
|
+
ok: false,
|
183
|
+
message: "Bloat detected:\n#{print_bloat}"
|
184
|
+
}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def duplicate_indexes
|
189
|
+
indexes = query_module.duplicate_indexes(in_format: :hash)
|
190
|
+
|
191
|
+
if indexes.count == 0
|
192
|
+
{
|
193
|
+
ok: true,
|
194
|
+
message: "No duplicate indexes detected."
|
195
|
+
}
|
196
|
+
else
|
197
|
+
print_indexes = indexes.map do |i|
|
198
|
+
"'#{i.fetch('idx1')}' of size #{i.fetch('size')} is identical to '#{i.fetch('idx2')}'"
|
199
|
+
end.join(",\n")
|
200
|
+
|
201
|
+
{
|
202
|
+
ok: false,
|
203
|
+
message: "Duplicate indexes detected:\n#{print_indexes}"
|
204
|
+
}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def outliers
|
209
|
+
queries = query_module.outliers(in_format: :hash).select do |q|
|
210
|
+
q.fetch("prop_exec_time").gsub("%", "").to_f >= PG_EXTRAS_OUTLIERS_MIN_EXEC_RATIO
|
211
|
+
end
|
212
|
+
|
213
|
+
if queries.count == 0
|
214
|
+
{
|
215
|
+
ok: true,
|
216
|
+
message: "No queries using significant execution ratio detected."
|
217
|
+
}
|
218
|
+
else
|
219
|
+
print_queries = queries.map do |q|
|
220
|
+
"'#{q.fetch('query').slice(0, 30)}...' called #{q.fetch('ncalls')} times, using #{q.fetch('prop_exec_time')} of total exec time."
|
221
|
+
end.join(",\n")
|
222
|
+
|
223
|
+
{
|
224
|
+
ok: false,
|
225
|
+
message: "Queries using significant execution ratio detected:\n#{print_queries}"
|
226
|
+
}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,48 @@
|
|
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 |key|
|
13
|
+
key[1].fetch(:ok) ? 1 : -1
|
14
|
+
end.map do |el|
|
15
|
+
key = el[0]
|
16
|
+
val = el[1]
|
17
|
+
symbol = val.fetch(:ok) ? "√" : "x"
|
18
|
+
color = val.fetch(:ok) ? :green : :red
|
19
|
+
|
20
|
+
[
|
21
|
+
colorize("[#{symbol}] - #{key}", color),
|
22
|
+
colorize(val.fetch(:message), color)
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
puts Terminal::Table.new(
|
27
|
+
title: title,
|
28
|
+
rows: rows
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def title
|
35
|
+
"ruby-pg-extras - diagnose report"
|
36
|
+
end
|
37
|
+
|
38
|
+
def colorize(string, color)
|
39
|
+
if color == :red
|
40
|
+
"\e[0;31;49m#{string}\e[0m"
|
41
|
+
elsif color == :green
|
42
|
+
"\e[0;32;49m#{string}\e[0m"
|
43
|
+
else
|
44
|
+
raise "Unsupported color: #{color}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -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;
|
data/lib/ruby-pg-extras.rb
CHANGED
@@ -3,13 +3,15 @@
|
|
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'
|
6
8
|
|
7
9
|
module RubyPGExtras
|
8
10
|
@@database_url = nil
|
9
11
|
NEW_PG_STAT_STATEMENTS = "1.8"
|
10
12
|
|
11
13
|
QUERIES = %i(
|
12
|
-
bloat blocking cache_hit db_settings
|
14
|
+
add_extensions bloat blocking cache_hit db_settings
|
13
15
|
calls extensions table_cache_hit index_cache_hit
|
14
16
|
index_size index_usage null_indexes locks all_locks
|
15
17
|
long_running_queries mandelbrot outliers
|
@@ -28,7 +30,7 @@ module RubyPGExtras
|
|
28
30
|
outliers_legacy: { limit: 10 },
|
29
31
|
buffercache_stats: { limit: 10 },
|
30
32
|
buffercache_usage: { limit: 20 },
|
31
|
-
unused_indexes: {
|
33
|
+
unused_indexes: { max_scans: 50 },
|
32
34
|
null_indexes: { min_relation_size_mb: 10 }
|
33
35
|
})
|
34
36
|
|
@@ -67,6 +69,18 @@ module RubyPGExtras
|
|
67
69
|
)
|
68
70
|
end
|
69
71
|
|
72
|
+
def self.diagnose(in_format: :display_table)
|
73
|
+
data = RubyPGExtras::DiagnoseData.call
|
74
|
+
|
75
|
+
if in_format == :display_table
|
76
|
+
RubyPGExtras::DiagnosePrint.call(data)
|
77
|
+
elsif in_format == :hash
|
78
|
+
data
|
79
|
+
else
|
80
|
+
raise "Invalid 'in_format' argument!"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
70
84
|
def self.display_result(result, title:, in_format:)
|
71
85
|
case in_format
|
72
86
|
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,76 @@
|
|
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" => "t", "null_frac" => "00.00%", "expected_saving" => "0 kb" },
|
24
|
+
{ "oid" => 321, "index" => "index_feedbacks_on_target_id", "index_size" => "80 kB", "unique" => "f", "null_frac" => "97.00%", "expected_saving" => "77 kb" },
|
25
|
+
{ "oid" => 231, "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "unique" => "t", "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
|
+
before do
|
59
|
+
expect(RubyPGExtras).to receive(:unused_indexes) {
|
60
|
+
[
|
61
|
+
{ "table" => "public.plans", "index" => "index_plans_on_payer_id", "index_size" => "2 MB", "index_scans" => 0 },
|
62
|
+
{ "table" => "public.feedbacks", "index" => "index_feedbacks_on_target_id", "index_size" => "80 kB", "index_scans" => 1 },
|
63
|
+
{ "table" => "public.channels", "index" => "index_channels_on_slack_id", "index_size" => "1.1 MB", "index_scans" => 7}
|
64
|
+
]
|
65
|
+
}
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
it "works" do
|
70
|
+
expect {
|
71
|
+
RubyPGExtras::DiagnosePrint.call(result)
|
72
|
+
}.not_to raise_error
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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
|
+
:table_cache_hit => {
|
13
|
+
:ok => false,
|
14
|
+
:message => "Table hit ratio too low: 0.906977."
|
15
|
+
},
|
16
|
+
:index_cache_hit => {
|
17
|
+
:ok => false,
|
18
|
+
:message => "Index hit ratio is too low: 0.818182."
|
19
|
+
},
|
20
|
+
:ssl_used => {
|
21
|
+
:ok => true,
|
22
|
+
:message => "Database client is using a secure SSL connection."
|
23
|
+
}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "call" do
|
28
|
+
it "works" do
|
29
|
+
expect {
|
30
|
+
print_result
|
31
|
+
}.not_to raise_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
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
|
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:
|
4
|
+
version: 3.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10-
|
11
|
+
date: 2021-10-28 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,9 @@ 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/queries/add_extensions.sql
|
86
103
|
- lib/ruby-pg-extras/queries/all_locks.sql
|
87
104
|
- lib/ruby-pg-extras/queries/bloat.sql
|
88
105
|
- lib/ruby-pg-extras/queries/blocking.sql
|
@@ -116,7 +133,10 @@ files:
|
|
116
133
|
- lib/ruby-pg-extras/queries/unused_indexes.sql
|
117
134
|
- lib/ruby-pg-extras/queries/vacuum_stats.sql
|
118
135
|
- lib/ruby-pg-extras/version.rb
|
136
|
+
- ruby-pg-extras-diagnose.png
|
119
137
|
- ruby-pg-extras.gemspec
|
138
|
+
- spec/diagnose_data_spec.rb
|
139
|
+
- spec/diagnose_print_spec.rb
|
120
140
|
- spec/smoke_spec.rb
|
121
141
|
- spec/spec_helper.rb
|
122
142
|
homepage: http://github.com/pawurb/ruby-pg-extras
|
@@ -143,5 +163,7 @@ signing_key:
|
|
143
163
|
specification_version: 4
|
144
164
|
summary: Ruby PostgreSQL performance database insights
|
145
165
|
test_files:
|
166
|
+
- spec/diagnose_data_spec.rb
|
167
|
+
- spec/diagnose_print_spec.rb
|
146
168
|
- spec/smoke_spec.rb
|
147
169
|
- spec/spec_helper.rb
|