ruby-pg-extras 5.3.0 → 5.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -2
- data/Gemfile +1 -1
- data/README.md +5 -1
- data/Rakefile +2 -2
- data/lib/ruby-pg-extras.rb +34 -32
- data/lib/ruby_pg_extras/diagnose_data.rb +24 -24
- data/lib/ruby_pg_extras/diagnose_print.rb +3 -3
- data/lib/ruby_pg_extras/index_info.rb +4 -4
- data/lib/ruby_pg_extras/index_info_print.rb +5 -5
- data/lib/ruby_pg_extras/queries/all_locks.sql +2 -0
- data/lib/ruby_pg_extras/queries/locks.sql +2 -0
- data/lib/ruby_pg_extras/queries/null_indexes.sql +1 -0
- data/lib/ruby_pg_extras/size_parser.rb +2 -2
- data/lib/ruby_pg_extras/table_info.rb +1 -1
- data/lib/ruby_pg_extras/table_info_print.rb +4 -4
- data/lib/ruby_pg_extras/version.rb +1 -1
- data/ruby-pg-extras.gemspec +13 -12
- data/spec/diagnose_data_spec.rb +6 -6
- data/spec/diagnose_print_spec.rb +5 -5
- data/spec/index_info_spec.rb +6 -6
- data/spec/size_parser_spec.rb +12 -12
- data/spec/smoke_spec.rb +3 -3
- data/spec/spec_helper.rb +13 -13
- data/spec/table_info_spec.rb +3 -3
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ddb04fe6f72f45595c16a533c58ef34fc59dc551ab78e131f0cec1ca4a56d3b
|
4
|
+
data.tar.gz: d8763349854102e5bfd2174461f7d3c43de7c74526dba14d1982c68705f59055
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b33851aaed74e98903aee07d92bf4a048300f7e99bec1e7cb541c14a7669cbdf2bd8c5d784d922f276c3c287d6cf7bb65df16a7788e2b58fdcd20ca1c3a4ec4
|
7
|
+
data.tar.gz: '085b52d651b6dbf60a5a44129416fccc8801e7be6c321fb0ac9083667902bdd7025a52a2e7dc20e6756f3062492820342f1aabbcf1bfe8be9b5c43bf0362d6a2'
|
data/.github/workflows/ci.yml
CHANGED
@@ -57,8 +57,7 @@ jobs:
|
|
57
57
|
ruby-version: ${{ matrix.ruby-version }}
|
58
58
|
- name: Setup dependencies
|
59
59
|
run: |
|
60
|
-
gem
|
61
|
-
gem install bundler
|
60
|
+
gem install bundler -v 2.4.22
|
62
61
|
sudo apt-get update --allow-releaseinfo-change
|
63
62
|
sudo apt install postgresql-client
|
64
63
|
sudo apt install libpq-dev
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -10,6 +10,8 @@ Alternative versions:
|
|
10
10
|
|
11
11
|
- [Ruby on Rails](https://github.com/pawurb/rails-pg-extras)
|
12
12
|
|
13
|
+
- [Rust](https://github.com/pawurb/rust-pg-extras)
|
14
|
+
|
13
15
|
- [NodeJS](https://github.com/pawurb/node-postgres-extras)
|
14
16
|
|
15
17
|
- [Elixir](https://github.com/pawurb/ecto_psql_extras)
|
@@ -98,6 +100,8 @@ RubyPgExtras.long_running_queries(args: { threshold: "200 milliseconds" })
|
|
98
100
|
|
99
101
|
```
|
100
102
|
|
103
|
+
You can customize the default `public` schema by setting `ENV['PG_EXTRAS_SCHEMA']` value.
|
104
|
+
|
101
105
|
## Diagnose report
|
102
106
|
|
103
107
|
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:
|
@@ -437,7 +441,7 @@ This command displays the total size of each table and materialized view in the
|
|
437
441
|
|
438
442
|
```ruby
|
439
443
|
|
440
|
-
RubyPgExtras.unused_indexes(args: { max_scans:
|
444
|
+
RubyPgExtras.unused_indexes(args: { max_scans: 50 })
|
441
445
|
|
442
446
|
table | index | index_size | index_scans
|
443
447
|
---------------------+--------------------------------------------+------------+-------------
|
data/Rakefile
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require
|
2
|
+
require "rspec/core/rake_task"
|
3
3
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
|
-
desc
|
6
|
+
desc "Test all PG versions"
|
7
7
|
task :test_all do
|
8
8
|
system("PG_VERSION=11 bundle exec rspec spec/ && PG_VERSION=12 bundle exec rspec spec/ && PG_VERSION=13 bundle exec rspec spec/ && PG_VERSION=14 bundle exec rspec spec/")
|
9
9
|
end
|
data/lib/ruby-pg-extras.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
3
|
+
require "terminal-table"
|
4
|
+
require "uri"
|
5
|
+
require "pg"
|
6
|
+
require "ruby_pg_extras/size_parser"
|
7
|
+
require "ruby_pg_extras/diagnose_data"
|
8
|
+
require "ruby_pg_extras/diagnose_print"
|
9
|
+
require "ruby_pg_extras/index_info"
|
10
|
+
require "ruby_pg_extras/index_info_print"
|
11
|
+
require "ruby_pg_extras/table_info"
|
12
|
+
require "ruby_pg_extras/table_info_print"
|
13
13
|
|
14
14
|
module RubyPgExtras
|
15
15
|
@@database_url = nil
|
@@ -27,6 +27,8 @@ module RubyPgExtras
|
|
27
27
|
buffercache_usage ssl_used connections
|
28
28
|
)
|
29
29
|
|
30
|
+
DEFAULT_SCHEMA = ENV["PG_EXTRAS_SCHEMA"] || "public"
|
31
|
+
|
30
32
|
DEFAULT_ARGS = Hash.new({}).merge({
|
31
33
|
calls: { limit: 10 },
|
32
34
|
calls_legacy: { limit: 10 },
|
@@ -37,18 +39,18 @@ module RubyPgExtras
|
|
37
39
|
outliers_legacy: { limit: 10 },
|
38
40
|
buffercache_stats: { limit: 10 },
|
39
41
|
buffercache_usage: { limit: 20 },
|
40
|
-
unused_indexes: { max_scans: 50, schema:
|
42
|
+
unused_indexes: { max_scans: 50, schema: DEFAULT_SCHEMA },
|
41
43
|
null_indexes: { min_relation_size_mb: 10 },
|
42
|
-
index_usage: { schema:
|
43
|
-
index_cache_hit: { schema:
|
44
|
-
table_cache_hit: { schema:
|
45
|
-
index_scans: { schema:
|
46
|
-
cache_hit: { schema:
|
47
|
-
seq_scans: { schema:
|
48
|
-
table_index_scans: { schema:
|
49
|
-
records_rank: { schema:
|
50
|
-
tables: { schema:
|
51
|
-
kill_pid: { pid: 0 }
|
44
|
+
index_usage: { schema: DEFAULT_SCHEMA },
|
45
|
+
index_cache_hit: { schema: DEFAULT_SCHEMA },
|
46
|
+
table_cache_hit: { schema: DEFAULT_SCHEMA },
|
47
|
+
index_scans: { schema: DEFAULT_SCHEMA },
|
48
|
+
cache_hit: { schema: DEFAULT_SCHEMA },
|
49
|
+
seq_scans: { schema: DEFAULT_SCHEMA },
|
50
|
+
table_index_scans: { schema: DEFAULT_SCHEMA },
|
51
|
+
records_rank: { schema: DEFAULT_SCHEMA },
|
52
|
+
tables: { schema: DEFAULT_SCHEMA },
|
53
|
+
kill_pid: { pid: 0 },
|
52
54
|
})
|
53
55
|
|
54
56
|
QUERIES.each do |query_name|
|
@@ -56,7 +58,7 @@ module RubyPgExtras
|
|
56
58
|
run_query(
|
57
59
|
query_name: query_name,
|
58
60
|
in_format: options.fetch(:in_format, :display_table),
|
59
|
-
args: options.fetch(:args, {})
|
61
|
+
args: options.fetch(:args, {}),
|
60
62
|
)
|
61
63
|
end
|
62
64
|
end
|
@@ -73,16 +75,16 @@ module RubyPgExtras
|
|
73
75
|
end
|
74
76
|
|
75
77
|
sql = if (custom_args = DEFAULT_ARGS[query_name].merge(args)) != {}
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
sql_for(query_name: query_name) % custom_args
|
79
|
+
else
|
80
|
+
sql_for(query_name: query_name)
|
81
|
+
end
|
80
82
|
result = connection.exec(sql)
|
81
83
|
|
82
84
|
display_result(
|
83
85
|
result,
|
84
86
|
title: description_for(query_name: query_name),
|
85
|
-
in_format: in_format
|
87
|
+
in_format: in_format,
|
86
88
|
)
|
87
89
|
end
|
88
90
|
|
@@ -138,15 +140,15 @@ module RubyPgExtras
|
|
138
140
|
result
|
139
141
|
when :display_table
|
140
142
|
headings = if result.count > 0
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
143
|
+
result[0].keys
|
144
|
+
else
|
145
|
+
["No results"]
|
146
|
+
end
|
145
147
|
|
146
148
|
puts Terminal::Table.new(
|
147
149
|
title: title,
|
148
150
|
headings: headings,
|
149
|
-
rows: result.values
|
151
|
+
rows: result.values,
|
150
152
|
)
|
151
153
|
else
|
152
154
|
raise "Invalid in_format option"
|
@@ -22,7 +22,7 @@ module RubyPgExtras
|
|
22
22
|
:unused_indexes,
|
23
23
|
:null_indexes,
|
24
24
|
:bloat,
|
25
|
-
:duplicate_indexes
|
25
|
+
:duplicate_indexes,
|
26
26
|
].yield_self do |checks|
|
27
27
|
extensions_data = query_module.extensions(in_format: :hash)
|
28
28
|
|
@@ -66,12 +66,12 @@ module RubyPgExtras
|
|
66
66
|
if table_cache_hit_ratio > min_expected
|
67
67
|
{
|
68
68
|
ok: true,
|
69
|
-
message: "Table cache hit ratio is correct: #{table_cache_hit_ratio}."
|
69
|
+
message: "Table cache hit ratio is correct: #{table_cache_hit_ratio}.",
|
70
70
|
}
|
71
71
|
else
|
72
72
|
{
|
73
73
|
ok: false,
|
74
|
-
message: "Table hit ratio is too low: #{table_cache_hit_ratio}."
|
74
|
+
message: "Table hit ratio is too low: #{table_cache_hit_ratio}.",
|
75
75
|
}
|
76
76
|
end
|
77
77
|
end
|
@@ -87,12 +87,12 @@ module RubyPgExtras
|
|
87
87
|
if index_cache_hit_ratio > min_expected
|
88
88
|
{
|
89
89
|
ok: true,
|
90
|
-
message: "Index hit ratio is correct: #{index_cache_hit_ratio}."
|
90
|
+
message: "Index hit ratio is correct: #{index_cache_hit_ratio}.",
|
91
91
|
}
|
92
92
|
else
|
93
93
|
{
|
94
94
|
ok: false,
|
95
|
-
message: "Index hit ratio is too low: #{index_cache_hit_ratio}."
|
95
|
+
message: "Index hit ratio is too low: #{index_cache_hit_ratio}.",
|
96
96
|
}
|
97
97
|
end
|
98
98
|
end
|
@@ -103,12 +103,12 @@ module RubyPgExtras
|
|
103
103
|
if ssl_connection
|
104
104
|
{
|
105
105
|
ok: true,
|
106
|
-
message: "Database client is using a secure SSL connection."
|
106
|
+
message: "Database client is using a secure SSL connection.",
|
107
107
|
}
|
108
108
|
else
|
109
109
|
{
|
110
110
|
ok: false,
|
111
|
-
message: "Database client is using an unencrypted connection."
|
111
|
+
message: "Database client is using an unencrypted connection.",
|
112
112
|
}
|
113
113
|
end
|
114
114
|
end
|
@@ -116,7 +116,7 @@ module RubyPgExtras
|
|
116
116
|
def unused_indexes
|
117
117
|
indexes = query_module.unused_indexes(
|
118
118
|
in_format: :hash,
|
119
|
-
args: { min_scans: PG_EXTRAS_UNUSED_INDEXES_MAX_SCANS }
|
119
|
+
args: { min_scans: PG_EXTRAS_UNUSED_INDEXES_MAX_SCANS },
|
120
120
|
).select do |i|
|
121
121
|
SizeParser.to_i(i.fetch("index_size").strip) >= PG_EXTRAS_UNUSED_INDEXES_MIN_SIZE_BYTES
|
122
122
|
end
|
@@ -124,15 +124,15 @@ module RubyPgExtras
|
|
124
124
|
if indexes.count == 0
|
125
125
|
{
|
126
126
|
ok: true,
|
127
|
-
message: "No unused indexes detected."
|
127
|
+
message: "No unused indexes detected.",
|
128
128
|
}
|
129
129
|
else
|
130
130
|
print_indexes = indexes.map do |i|
|
131
|
-
"'#{i.fetch(
|
131
|
+
"'#{i.fetch("index")}' on '#{i.fetch("table")}' size #{i.fetch("index_size")}"
|
132
132
|
end.join(",\n")
|
133
133
|
{
|
134
134
|
ok: false,
|
135
|
-
message: "Unused indexes detected:\n#{print_indexes}"
|
135
|
+
message: "Unused indexes detected:\n#{print_indexes}",
|
136
136
|
}
|
137
137
|
end
|
138
138
|
end
|
@@ -140,7 +140,7 @@ module RubyPgExtras
|
|
140
140
|
def null_indexes
|
141
141
|
indexes = query_module.null_indexes(
|
142
142
|
in_format: :hash,
|
143
|
-
args: { min_relation_size_mb: PG_EXTRAS_NULL_INDEXES_MIN_SIZE_MB }
|
143
|
+
args: { min_relation_size_mb: PG_EXTRAS_NULL_INDEXES_MIN_SIZE_MB },
|
144
144
|
).select do |i|
|
145
145
|
i.fetch("null_frac").gsub("%", "").to_f >= PG_EXTRAS_NULL_MIN_NULL_FRAC_PERCENT
|
146
146
|
end
|
@@ -148,15 +148,15 @@ module RubyPgExtras
|
|
148
148
|
if indexes.count == 0
|
149
149
|
{
|
150
150
|
ok: true,
|
151
|
-
message: "No null indexes detected."
|
151
|
+
message: "No null indexes detected.",
|
152
152
|
}
|
153
153
|
else
|
154
154
|
print_indexes = indexes.map do |i|
|
155
|
-
"'#{i.fetch(
|
155
|
+
"'#{i.fetch("index")}' size #{i.fetch("index_size")} null values fraction #{i.fetch("null_frac")}"
|
156
156
|
end.join(",\n")
|
157
157
|
{
|
158
158
|
ok: false,
|
159
|
-
message: "Null indexes detected:\n#{print_indexes}"
|
159
|
+
message: "Null indexes detected:\n#{print_indexes}",
|
160
160
|
}
|
161
161
|
end
|
162
162
|
end
|
@@ -169,16 +169,16 @@ module RubyPgExtras
|
|
169
169
|
if bloat_data.count == 0
|
170
170
|
{
|
171
171
|
ok: true,
|
172
|
-
message: "No bloat detected."
|
172
|
+
message: "No bloat detected.",
|
173
173
|
}
|
174
174
|
else
|
175
175
|
print_bloat = bloat_data.map do |b|
|
176
|
-
"'#{b.fetch(
|
176
|
+
"'#{b.fetch("object_name")}' bloat #{b.fetch("bloat")} waste #{b.fetch("waste")}"
|
177
177
|
end.join(",\n")
|
178
178
|
|
179
179
|
{
|
180
180
|
ok: false,
|
181
|
-
message: "Bloat detected:\n#{print_bloat}"
|
181
|
+
message: "Bloat detected:\n#{print_bloat}",
|
182
182
|
}
|
183
183
|
end
|
184
184
|
end
|
@@ -189,16 +189,16 @@ module RubyPgExtras
|
|
189
189
|
if indexes.count == 0
|
190
190
|
{
|
191
191
|
ok: true,
|
192
|
-
message: "No duplicate indexes detected."
|
192
|
+
message: "No duplicate indexes detected.",
|
193
193
|
}
|
194
194
|
else
|
195
195
|
print_indexes = indexes.map do |i|
|
196
|
-
"'#{i.fetch(
|
196
|
+
"'#{i.fetch("idx1")}' of size #{i.fetch("size")} is identical to '#{i.fetch("idx2")}'"
|
197
197
|
end.join(",\n")
|
198
198
|
|
199
199
|
{
|
200
200
|
ok: false,
|
201
|
-
message: "Duplicate indexes detected:\n#{print_indexes}"
|
201
|
+
message: "Duplicate indexes detected:\n#{print_indexes}",
|
202
202
|
}
|
203
203
|
end
|
204
204
|
end
|
@@ -211,16 +211,16 @@ module RubyPgExtras
|
|
211
211
|
if queries.count == 0
|
212
212
|
{
|
213
213
|
ok: true,
|
214
|
-
message: "No queries using significant execution ratio detected."
|
214
|
+
message: "No queries using significant execution ratio detected.",
|
215
215
|
}
|
216
216
|
else
|
217
217
|
print_queries = queries.map do |q|
|
218
|
-
"'#{q.fetch(
|
218
|
+
"'#{q.fetch("query").slice(0, 30)}...' called #{q.fetch("ncalls")} times, using #{q.fetch("prop_exec_time")} of total exec time."
|
219
219
|
end.join(",\n")
|
220
220
|
|
221
221
|
{
|
222
222
|
ok: false,
|
223
|
-
message: "Queries using significant execution ratio detected:\n#{print_queries}"
|
223
|
+
message: "Queries using significant execution ratio detected:\n#{print_queries}",
|
224
224
|
}
|
225
225
|
end
|
226
226
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "terminal-table"
|
4
4
|
|
5
5
|
module RubyPgExtras
|
6
6
|
class DiagnosePrint
|
@@ -17,13 +17,13 @@ module RubyPgExtras
|
|
17
17
|
|
18
18
|
[
|
19
19
|
colorize("[#{symbol}] - #{el.fetch(:check_name)}", color),
|
20
|
-
colorize(el.fetch(:message), color)
|
20
|
+
colorize(el.fetch(:message), color),
|
21
21
|
]
|
22
22
|
end
|
23
23
|
|
24
24
|
puts Terminal::Table.new(
|
25
25
|
title: title,
|
26
|
-
rows: rows
|
26
|
+
rows: rows,
|
27
27
|
)
|
28
28
|
end
|
29
29
|
|
@@ -19,16 +19,16 @@ module RubyPgExtras
|
|
19
19
|
{
|
20
20
|
index_name: index_name,
|
21
21
|
table_name: index_data.fetch("tablename"),
|
22
|
-
columns: index_data.fetch("columns").split(
|
22
|
+
columns: index_data.fetch("columns").split(",").map(&:strip),
|
23
23
|
index_size: index_size_data.find do |el|
|
24
24
|
el.fetch("name") == index_name
|
25
25
|
end.fetch("size", "N/A"),
|
26
|
-
index_scans:
|
26
|
+
index_scans: index_scans_data.find do |el|
|
27
27
|
el.fetch("index") == index_name
|
28
28
|
end.fetch("index_scans", "N/A"),
|
29
29
|
null_frac: null_indexes_data.find do |el|
|
30
30
|
el.fetch("index") == index_name
|
31
|
-
end&.fetch("null_frac", "N/A")&.strip || "0.00%"
|
31
|
+
end&.fetch("null_frac", "N/A")&.strip || "0.00%",
|
32
32
|
}
|
33
33
|
end
|
34
34
|
end
|
@@ -40,7 +40,7 @@ module RubyPgExtras
|
|
40
40
|
def null_indexes_data
|
41
41
|
@_null_indexes_data ||= query_module.null_indexes(
|
42
42
|
in_format: :hash,
|
43
|
-
args: { min_relation_size_mb: 0 }
|
43
|
+
args: { min_relation_size_mb: 0 },
|
44
44
|
)
|
45
45
|
end
|
46
46
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "terminal-table"
|
4
4
|
|
5
5
|
module RubyPgExtras
|
6
6
|
class IndexInfoPrint
|
@@ -13,10 +13,10 @@ module RubyPgExtras
|
|
13
13
|
[
|
14
14
|
el.fetch(:index_name),
|
15
15
|
el.fetch(:table_name),
|
16
|
-
el.fetch(:columns).join(
|
16
|
+
el.fetch(:columns).join(", "),
|
17
17
|
el.fetch(:index_size),
|
18
18
|
el.fetch(:index_scans),
|
19
|
-
el.fetch(:null_frac)
|
19
|
+
el.fetch(:null_frac),
|
20
20
|
]
|
21
21
|
end
|
22
22
|
|
@@ -27,10 +27,10 @@ module RubyPgExtras
|
|
27
27
|
"Columns",
|
28
28
|
"Index size",
|
29
29
|
"Index scans",
|
30
|
-
"Null frac"
|
30
|
+
"Null frac",
|
31
31
|
],
|
32
32
|
title: title,
|
33
|
-
rows: rows
|
33
|
+
rows: rows,
|
34
34
|
)
|
35
35
|
end
|
36
36
|
|
@@ -10,7 +10,7 @@ module RubyPgExtras
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.regexp_for_units(units)
|
13
|
-
/\A(-?\d+)\s?(#{units.join(
|
13
|
+
/\A(-?\d+)\s?(#{units.join("|")})\z/i
|
14
14
|
end
|
15
15
|
|
16
16
|
SI_UNITS = %w[bytes kB MB GB TB PB EB ZB YB].map(&:downcase).freeze
|
@@ -32,7 +32,7 @@ module RubyPgExtras
|
|
32
32
|
return nil unless match_data
|
33
33
|
|
34
34
|
exponent = units.index(match_data[2].downcase).to_i
|
35
|
-
match_data[1].to_i * multiplier**exponent
|
35
|
+
match_data[1].to_i * multiplier ** exponent
|
36
36
|
end
|
37
37
|
|
38
38
|
DIGITS_ONLY_REGEXP = /\A(-?\d+)\z/.freeze
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "terminal-table"
|
4
4
|
|
5
5
|
module RubyPgExtras
|
6
6
|
class TableInfoPrint
|
@@ -17,7 +17,7 @@ module RubyPgExtras
|
|
17
17
|
el.fetch(:indexes_cache_hit),
|
18
18
|
el.fetch(:estimated_rows),
|
19
19
|
el.fetch(:sequential_scans),
|
20
|
-
el.fetch(:indexes_scans)
|
20
|
+
el.fetch(:indexes_scans),
|
21
21
|
]
|
22
22
|
end
|
23
23
|
|
@@ -29,10 +29,10 @@ module RubyPgExtras
|
|
29
29
|
"Indexes cache hit",
|
30
30
|
"Estimated rows",
|
31
31
|
"Sequential scans",
|
32
|
-
"Indexes scans"
|
32
|
+
"Indexes scans",
|
33
33
|
],
|
34
34
|
title: title,
|
35
|
-
rows: rows
|
35
|
+
rows: rows,
|
36
36
|
)
|
37
37
|
end
|
38
38
|
|
data/ruby-pg-extras.gemspec
CHANGED
@@ -1,24 +1,25 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "ruby_pg_extras/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name
|
8
|
-
s.version
|
9
|
-
s.authors
|
10
|
-
s.email
|
11
|
-
s.summary
|
12
|
-
s.description
|
13
|
-
s.homepage
|
14
|
-
s.files
|
15
|
-
s.test_files
|
7
|
+
s.name = "ruby-pg-extras"
|
8
|
+
s.version = RubyPgExtras::VERSION
|
9
|
+
s.authors = ["pawurb"]
|
10
|
+
s.email = ["contact@pawelurbanek.com"]
|
11
|
+
s.summary = %q{ Ruby PostgreSQL performance database insights }
|
12
|
+
s.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
|
+
s.homepage = "http://github.com/pawurb/ruby-pg-extras"
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = s.files.grep(%r{^(spec)/})
|
16
16
|
s.require_paths = ["lib"]
|
17
|
-
s.license
|
17
|
+
s.license = "MIT"
|
18
18
|
s.add_dependency "pg"
|
19
19
|
s.add_dependency "terminal-table"
|
20
20
|
s.add_development_dependency "rake"
|
21
21
|
s.add_development_dependency "rspec"
|
22
|
+
s.add_development_dependency "rufo"
|
22
23
|
|
23
24
|
if s.respond_to?(:metadata=)
|
24
25
|
s.metadata = { "rubygems_mfa_required" => "true" }
|
data/spec/diagnose_data_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe RubyPgExtras::DiagnoseData do
|
6
6
|
subject(:result) do
|
@@ -14,7 +14,7 @@ describe RubyPgExtras::DiagnoseData do
|
|
14
14
|
[
|
15
15
|
{ "table" => "public.plans", "index" => "index_plans_on_payer_id", "index_size" => "16 MB", "index_scans" => 0 },
|
16
16
|
{ "table" => "public.feedbacks", "index" => "index_feedbacks_on_target_id", "index_size" => "111180 bytes", "index_scans" => 1 },
|
17
|
-
{ "table" => "public.channels", "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "index_scans" => 7}
|
17
|
+
{ "table" => "public.channels", "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "index_scans" => 7 },
|
18
18
|
]
|
19
19
|
}
|
20
20
|
|
@@ -22,7 +22,7 @@ describe RubyPgExtras::DiagnoseData do
|
|
22
22
|
[
|
23
23
|
{ "oid" => 123, "index" => "index_plans_on_payer_id", "index_size" => "16 MB", "unique" => true, "null_frac" => "00.00%", "expected_saving" => "0 kb" },
|
24
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" }
|
25
|
+
{ "oid" => 231, "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "unique" => true, "null_frac" => "49.99%", "expected_saving" => "28 MB" },
|
26
26
|
]
|
27
27
|
}
|
28
28
|
|
@@ -30,19 +30,19 @@ describe RubyPgExtras::DiagnoseData do
|
|
30
30
|
[
|
31
31
|
{ "type" => "table", "schemaname" => "public", "object_name" => "bloated_table_1", "bloat" => 8, "waste" => "0 kb" },
|
32
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" }
|
33
|
+
{ "type" => "schemaname", "public" => "index_channels_on_slack_id", "object_name" => "bloated_index", "bloat" => 11, "waste" => "28 MB" },
|
34
34
|
]
|
35
35
|
}
|
36
36
|
|
37
37
|
expect(RubyPgExtras).to receive(:duplicate_indexes) {
|
38
38
|
[
|
39
|
-
{ "size" => "128 kb", "idx1" => "users_pkey", "idx2" => "index_users_id" }
|
39
|
+
{ "size" => "128 kb", "idx1" => "users_pkey", "idx2" => "index_users_id" },
|
40
40
|
]
|
41
41
|
}
|
42
42
|
|
43
43
|
expect(RubyPgExtras).to receive(:outliers) {
|
44
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" }
|
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
46
|
]
|
47
47
|
}
|
48
48
|
end
|
data/spec/diagnose_print_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe RubyPgExtras::DiagnosePrint do
|
6
6
|
subject(:print_result) do
|
@@ -12,18 +12,18 @@ describe RubyPgExtras::DiagnosePrint do
|
|
12
12
|
{
|
13
13
|
:check_name => :table_cache_hit,
|
14
14
|
:ok => false,
|
15
|
-
:message => "Table hit ratio too low: 0.906977."
|
15
|
+
:message => "Table hit ratio too low: 0.906977.",
|
16
16
|
},
|
17
17
|
{
|
18
18
|
:check_name => :index_cache_hit,
|
19
19
|
:ok => false,
|
20
|
-
:message => "Index hit ratio is too low: 0.818182."
|
20
|
+
:message => "Index hit ratio is too low: 0.818182.",
|
21
21
|
},
|
22
22
|
{
|
23
23
|
:check_name => :ssl_used,
|
24
24
|
:ok => true,
|
25
|
-
:message => "Database client is using a secure SSL connection."
|
26
|
-
}
|
25
|
+
:message => "Database client is using a secure SSL connection.",
|
26
|
+
},
|
27
27
|
]
|
28
28
|
end
|
29
29
|
|
data/spec/index_info_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe RubyPgExtras::IndexInfo do
|
6
6
|
subject(:result) do
|
@@ -13,27 +13,27 @@ describe RubyPgExtras::IndexInfo do
|
|
13
13
|
expect(RubyPgExtras).to receive(:indexes) {
|
14
14
|
[
|
15
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" },
|
16
|
+
{ "schemaname" => "public", "indexname" => "index_teams_on_slack_id", "tablename" => "teams", "columns" => "slack_id" },
|
17
17
|
]
|
18
18
|
}
|
19
19
|
|
20
20
|
expect(RubyPgExtras).to receive(:index_size) {
|
21
21
|
[
|
22
22
|
{ "name" => "index_users_on_api_auth_token", "size" => "1744 kB" },
|
23
|
-
{"name" => "index_teams_on_slack_id", "size" => "500 kB"},
|
23
|
+
{ "name" => "index_teams_on_slack_id", "size" => "500 kB" },
|
24
24
|
]
|
25
25
|
}
|
26
26
|
|
27
27
|
expect(RubyPgExtras).to receive(:null_indexes) {
|
28
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" }
|
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
30
|
]
|
31
31
|
}
|
32
32
|
|
33
33
|
expect(RubyPgExtras).to receive(:index_scans) {
|
34
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 }
|
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
37
|
]
|
38
38
|
}
|
39
39
|
end
|
data/spec/size_parser_spec.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe RubyPgExtras::SizeParser do
|
6
6
|
subject(:result) { described_class.to_i(arg) }
|
7
7
|
|
8
|
-
describe
|
8
|
+
describe "SI Units" do
|
9
9
|
let(:arg) { "#{num_units} #{unit}" }
|
10
10
|
|
11
11
|
context "when the argument is a number followed by 'bytes', with possible case variations" do
|
@@ -72,7 +72,7 @@ describe RubyPgExtras::SizeParser do
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
describe
|
75
|
+
describe "Binary Units" do
|
76
76
|
let(:arg) { "#{num_units} #{unit}" }
|
77
77
|
|
78
78
|
context "when the argument is a number followed by 'bytes', with possible case variations" do
|
@@ -139,28 +139,28 @@ describe RubyPgExtras::SizeParser do
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
-
context
|
143
|
-
let(:arg) {
|
142
|
+
context "when the argument has only digits" do
|
143
|
+
let(:arg) { "654245" }
|
144
144
|
|
145
145
|
it { is_expected.to eq(arg.to_i) }
|
146
146
|
end
|
147
147
|
|
148
|
-
describe
|
149
|
-
it
|
148
|
+
describe "errors" do
|
149
|
+
it "raises an error when the argument has an invalid prefix" do
|
150
150
|
expect do
|
151
|
-
described_class.to_i(
|
151
|
+
described_class.to_i("123 qb")
|
152
152
|
end.to raise_error ArgumentError
|
153
153
|
end
|
154
154
|
|
155
|
-
it
|
155
|
+
it "raises an error when the argument does not have a unit in bytes" do
|
156
156
|
expect do
|
157
|
-
described_class.to_i(
|
157
|
+
described_class.to_i("123 mL")
|
158
158
|
end.to raise_error ArgumentError
|
159
159
|
end
|
160
160
|
|
161
|
-
it
|
161
|
+
it "when the argument cannot be parsed an number of units" do
|
162
162
|
expect do
|
163
|
-
described_class.to_i(
|
163
|
+
described_class.to_i("1c3 MB")
|
164
164
|
end.to raise_error ArgumentError
|
165
165
|
end
|
166
166
|
end
|
data/spec/smoke_spec.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe RubyPgExtras do
|
6
6
|
RubyPgExtras::QUERIES.each do |query_name|
|
7
7
|
it "#{query_name} description can be read" do
|
8
8
|
expect do
|
9
9
|
RubyPgExtras.description_for(
|
10
|
-
query_name: query_name
|
10
|
+
query_name: query_name,
|
11
11
|
)
|
12
12
|
end.not_to raise_error
|
13
13
|
end
|
@@ -18,7 +18,7 @@ describe RubyPgExtras do
|
|
18
18
|
expect do
|
19
19
|
RubyPgExtras.run_query(
|
20
20
|
query_name: query_name,
|
21
|
-
in_format: :hash
|
21
|
+
in_format: :hash,
|
22
22
|
)
|
23
23
|
end.not_to raise_error
|
24
24
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require_relative
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler/setup"
|
5
|
+
require_relative "../lib/ruby-pg-extras"
|
6
6
|
|
7
7
|
pg_version = ENV["PG_VERSION"]
|
8
8
|
|
9
9
|
port = if pg_version == "11"
|
10
|
-
|
11
|
-
elsif pg_version == "12"
|
12
|
-
|
13
|
-
elsif pg_version == "13"
|
14
|
-
|
15
|
-
elsif pg_version == "14"
|
16
|
-
|
17
|
-
else
|
18
|
-
|
19
|
-
end
|
10
|
+
"5432"
|
11
|
+
elsif pg_version == "12"
|
12
|
+
"5433"
|
13
|
+
elsif pg_version == "13"
|
14
|
+
"5434"
|
15
|
+
elsif pg_version == "14"
|
16
|
+
"5435"
|
17
|
+
else
|
18
|
+
"5432"
|
19
|
+
end
|
20
20
|
|
21
21
|
ENV["DATABASE_URL"] ||= "postgresql://postgres:secret@localhost:#{port}/ruby-pg-extras-test"
|
22
22
|
|
data/spec/table_info_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe RubyPgExtras::TableInfo do
|
6
6
|
subject(:result) do
|
@@ -13,14 +13,14 @@ describe RubyPgExtras::TableInfo do
|
|
13
13
|
expect(RubyPgExtras).to receive(:tables) {
|
14
14
|
[
|
15
15
|
{ "schemaname" => "public", "tablename" => "users" },
|
16
|
-
{ "schemaname" => "public", "tablename" => "teams" }
|
16
|
+
{ "schemaname" => "public", "tablename" => "teams" },
|
17
17
|
]
|
18
18
|
}
|
19
19
|
|
20
20
|
expect(RubyPgExtras).to receive(:table_size) {
|
21
21
|
[
|
22
22
|
{ "name" => "teams", "size" => "25 MB" },
|
23
|
-
{"name" => "users", "size" => "250 MB"},
|
23
|
+
{ "name" => "users", "size" => "250 MB" },
|
24
24
|
]
|
25
25
|
}
|
26
26
|
|
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: 5.
|
4
|
+
version: 5.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rufo
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
description: " Ruby port of Heroku PG Extras. The goal of this project is to provide
|
70
84
|
a powerful insights into PostgreSQL database for Ruby on Rails apps that are not
|
71
85
|
using the default Heroku PostgreSQL plugin. "
|
@@ -144,7 +158,7 @@ licenses:
|
|
144
158
|
- MIT
|
145
159
|
metadata:
|
146
160
|
rubygems_mfa_required: 'true'
|
147
|
-
post_install_message:
|
161
|
+
post_install_message:
|
148
162
|
rdoc_options: []
|
149
163
|
require_paths:
|
150
164
|
- lib
|
@@ -159,8 +173,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
173
|
- !ruby/object:Gem::Version
|
160
174
|
version: '0'
|
161
175
|
requirements: []
|
162
|
-
rubygems_version: 3.
|
163
|
-
signing_key:
|
176
|
+
rubygems_version: 3.5.4
|
177
|
+
signing_key:
|
164
178
|
specification_version: 4
|
165
179
|
summary: Ruby PostgreSQL performance database insights
|
166
180
|
test_files:
|