pghero 3.1.0 → 3.7.0
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/CHANGELOG.md +78 -1
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/app/assets/javascripts/pghero/Chart.bundle.js +23379 -19766
- data/app/assets/javascripts/pghero/application.js +27 -12
- data/app/assets/javascripts/pghero/chartkick.js +834 -764
- data/app/assets/javascripts/pghero/highlight.min.js +440 -0
- data/app/assets/javascripts/pghero/jquery.js +318 -197
- data/app/assets/javascripts/pghero/nouislider.js +676 -1066
- data/app/assets/stylesheets/pghero/application.css +108 -2
- data/app/assets/stylesheets/pghero/nouislider.css +4 -10
- data/app/controllers/pg_hero/home_controller.rb +53 -16
- data/app/helpers/pg_hero/home_helper.rb +3 -3
- data/app/views/layouts/pg_hero/application.html.erb +3 -3
- data/app/views/pg_hero/home/_connections_table.html.erb +1 -1
- data/app/views/pg_hero/home/_live_queries_table.html.erb +8 -8
- data/app/views/pg_hero/home/_queries_table.html.erb +5 -5
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +8 -8
- data/app/views/pg_hero/home/_suggested_index.html.erb +6 -5
- data/app/views/pg_hero/home/connections.html.erb +12 -12
- data/app/views/pg_hero/home/explain.html.erb +2 -2
- data/app/views/pg_hero/home/index.html.erb +22 -20
- data/app/views/pg_hero/home/index_bloat.html.erb +6 -6
- data/app/views/pg_hero/home/maintenance.html.erb +3 -3
- data/app/views/pg_hero/home/queries.html.erb +7 -5
- data/app/views/pg_hero/home/relation_space.html.erb +4 -4
- data/app/views/pg_hero/home/show_query.html.erb +35 -31
- data/app/views/pg_hero/home/space.html.erb +50 -46
- data/app/views/pg_hero/home/system.html.erb +18 -18
- data/app/views/pg_hero/home/tune.html.erb +2 -2
- data/lib/generators/pghero/query_stats_generator.rb +1 -0
- data/lib/generators/pghero/space_stats_generator.rb +1 -0
- data/lib/pghero/database.rb +2 -2
- data/lib/pghero/engine.rb +4 -3
- data/lib/pghero/methods/basic.rb +26 -31
- data/lib/pghero/methods/connections.rb +4 -4
- data/lib/pghero/methods/constraints.rb +1 -1
- data/lib/pghero/methods/explain.rb +4 -3
- data/lib/pghero/methods/indexes.rb +8 -8
- data/lib/pghero/methods/kill.rb +1 -1
- data/lib/pghero/methods/maintenance.rb +3 -3
- data/lib/pghero/methods/queries.rb +2 -2
- data/lib/pghero/methods/query_stats.rb +34 -24
- data/lib/pghero/methods/replication.rb +2 -2
- data/lib/pghero/methods/sequences.rb +10 -5
- data/lib/pghero/methods/settings.rb +8 -1
- data/lib/pghero/methods/space.rb +20 -14
- data/lib/pghero/methods/suggested_indexes.rb +14 -7
- data/lib/pghero/methods/system.rb +12 -6
- data/lib/pghero/methods/tables.rb +4 -5
- data/lib/pghero/version.rb +1 -1
- data/lib/pghero.rb +35 -36
- data/lib/tasks/pghero.rake +11 -1
- data/licenses/LICENSE-chart.js.txt +1 -1
- data/licenses/LICENSE-date-fns.txt +21 -20
- data/licenses/LICENSE-kurkle-color.txt +9 -0
- metadata +8 -11
- data/app/assets/javascripts/pghero/highlight.pack.js +0 -2
data/lib/pghero/methods/basic.rb
CHANGED
@@ -31,21 +31,26 @@ module PgHero
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def quote_ident(value)
|
34
|
-
|
34
|
+
with_connection { |c| c.quote_column_name(value) }
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
|
-
def select_all(sql,
|
40
|
-
|
39
|
+
def select_all(sql, stats: false, query_columns: [])
|
40
|
+
with_connection(stats: stats) do |conn|
|
41
|
+
select_all_leased(sql, conn: conn, query_columns: query_columns)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def select_all_leased(sql, conn:, query_columns:)
|
41
46
|
# squish for logs
|
42
47
|
retries = 0
|
43
48
|
begin
|
44
49
|
result = conn.select_all(add_source(squish(sql)))
|
45
|
-
if ActiveRecord::VERSION::
|
46
|
-
result = result.map(&:symbolize_keys)
|
50
|
+
if ActiveRecord::VERSION::MAJOR >= 8
|
51
|
+
result = result.to_a.map(&:symbolize_keys)
|
47
52
|
else
|
48
|
-
result = result.map
|
53
|
+
result = result.map(&:symbolize_keys)
|
49
54
|
end
|
50
55
|
if filter_data
|
51
56
|
query_columns.each do |column|
|
@@ -81,7 +86,7 @@ module PgHero
|
|
81
86
|
end
|
82
87
|
|
83
88
|
def select_all_stats(sql, **options)
|
84
|
-
select_all(sql, **options,
|
89
|
+
select_all(sql, **options, stats: true)
|
85
90
|
end
|
86
91
|
|
87
92
|
def select_all_size(sql)
|
@@ -92,35 +97,21 @@ module PgHero
|
|
92
97
|
result
|
93
98
|
end
|
94
99
|
|
95
|
-
def select_one(sql
|
96
|
-
select_all(sql
|
97
|
-
end
|
98
|
-
|
99
|
-
def select_one_stats(sql)
|
100
|
-
select_one(sql, conn: stats_connection)
|
100
|
+
def select_one(sql)
|
101
|
+
select_all(sql).first.values.first
|
101
102
|
end
|
102
103
|
|
103
104
|
def execute(sql)
|
104
|
-
|
105
|
+
with_connection { |c| c.execute(add_source(sql)) }
|
105
106
|
end
|
106
107
|
|
107
|
-
def
|
108
|
-
connection_model
|
108
|
+
def with_connection(stats: false, &block)
|
109
|
+
model = stats ? ::PgHero::Stats : connection_model
|
110
|
+
model.connection_pool.with_connection(&block)
|
109
111
|
end
|
110
112
|
|
111
|
-
def stats_connection
|
112
|
-
::PgHero::Stats.connection
|
113
|
-
end
|
114
|
-
|
115
|
-
def insert_stats(table, columns, values)
|
116
|
-
values = values.map { |v| "(#{v.map { |v2| quote(v2) }.join(",")})" }.join(",")
|
117
|
-
columns = columns.map { |v| quote_table_name(v) }.join(",")
|
118
|
-
stats_connection.execute("INSERT INTO #{quote_table_name(table)} (#{columns}) VALUES #{values}")
|
119
|
-
end
|
120
|
-
|
121
|
-
# from ActiveSupport
|
122
113
|
def squish(str)
|
123
|
-
str.to_s.
|
114
|
+
str.to_s.squish
|
124
115
|
end
|
125
116
|
|
126
117
|
def add_source(sql)
|
@@ -128,11 +119,15 @@ module PgHero
|
|
128
119
|
end
|
129
120
|
|
130
121
|
def quote(value)
|
131
|
-
|
122
|
+
with_connection { |c| c.quote(value) }
|
132
123
|
end
|
133
124
|
|
134
125
|
def quote_table_name(value)
|
135
|
-
|
126
|
+
with_connection { |c| c.quote_table_name(value) }
|
127
|
+
end
|
128
|
+
|
129
|
+
def quote_column_name(value)
|
130
|
+
with_connection { |c| c.quote_column_name(value) }
|
136
131
|
end
|
137
132
|
|
138
133
|
def unquote(part)
|
@@ -153,7 +148,7 @@ module PgHero
|
|
153
148
|
end
|
154
149
|
|
155
150
|
def table_exists?(table)
|
156
|
-
|
151
|
+
with_connection(stats: true) { |c| c.table_exists?(table) }
|
157
152
|
end
|
158
153
|
end
|
159
154
|
end
|
@@ -3,7 +3,7 @@ module PgHero
|
|
3
3
|
module Connections
|
4
4
|
def connections
|
5
5
|
if server_version_num >= 90500
|
6
|
-
select_all
|
6
|
+
select_all <<~SQL
|
7
7
|
SELECT
|
8
8
|
pg_stat_activity.pid,
|
9
9
|
datname AS database,
|
@@ -20,7 +20,7 @@ module PgHero
|
|
20
20
|
pg_stat_activity.pid
|
21
21
|
SQL
|
22
22
|
else
|
23
|
-
select_all
|
23
|
+
select_all <<~SQL
|
24
24
|
SELECT
|
25
25
|
pid,
|
26
26
|
datname AS database,
|
@@ -41,7 +41,7 @@ module PgHero
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def connection_states
|
44
|
-
states = select_all
|
44
|
+
states = select_all <<~SQL
|
45
45
|
SELECT
|
46
46
|
state,
|
47
47
|
COUNT(*) AS connections
|
@@ -57,7 +57,7 @@ module PgHero
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def connection_sources
|
60
|
-
select_all
|
60
|
+
select_all <<~SQL
|
61
61
|
SELECT
|
62
62
|
datname AS database,
|
63
63
|
usename AS user,
|
@@ -9,10 +9,10 @@ module PgHero
|
|
9
9
|
|
10
10
|
# use transaction for safety
|
11
11
|
with_transaction(statement_timeout: (explain_timeout_sec * 1000).round, rollback: true) do
|
12
|
-
if (sql.
|
12
|
+
if (sql.delete_suffix(";").include?(";") || sql.upcase.include?("COMMIT")) && !explain_safe?
|
13
13
|
raise ActiveRecord::StatementInvalid, "Unsafe statement"
|
14
14
|
end
|
15
|
-
explanation =
|
15
|
+
explanation = execute("EXPLAIN #{sql}").map { |v| v["QUERY PLAN"] }.join("\n")
|
16
16
|
end
|
17
17
|
|
18
18
|
explanation
|
@@ -20,11 +20,12 @@ module PgHero
|
|
20
20
|
|
21
21
|
# TODO rename to explain in 4.0
|
22
22
|
# note: this method is not affected by the explain option
|
23
|
-
def explain_v2(sql, analyze: nil, verbose: nil, costs: nil, settings: nil, buffers: nil, wal: nil, timing: nil, summary: nil, format: "text")
|
23
|
+
def explain_v2(sql, analyze: nil, verbose: nil, costs: nil, settings: nil, generic_plan: nil, buffers: nil, wal: nil, timing: nil, summary: nil, format: "text")
|
24
24
|
options = []
|
25
25
|
add_explain_option(options, "ANALYZE", analyze)
|
26
26
|
add_explain_option(options, "VERBOSE", verbose)
|
27
27
|
add_explain_option(options, "SETTINGS", settings)
|
28
|
+
add_explain_option(options, "GENERIC_PLAN", generic_plan)
|
28
29
|
add_explain_option(options, "COSTS", costs)
|
29
30
|
add_explain_option(options, "BUFFERS", buffers)
|
30
31
|
add_explain_option(options, "WAL", wal)
|
@@ -2,7 +2,7 @@ module PgHero
|
|
2
2
|
module Methods
|
3
3
|
module Indexes
|
4
4
|
def index_hit_rate
|
5
|
-
select_one
|
5
|
+
select_one <<~SQL
|
6
6
|
SELECT
|
7
7
|
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read), 0) AS rate
|
8
8
|
FROM
|
@@ -11,7 +11,7 @@ module PgHero
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def index_caching
|
14
|
-
select_all
|
14
|
+
select_all <<~SQL
|
15
15
|
SELECT
|
16
16
|
schemaname AS schema,
|
17
17
|
relname AS table,
|
@@ -29,7 +29,7 @@ module PgHero
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def index_usage
|
32
|
-
select_all
|
32
|
+
select_all <<~SQL
|
33
33
|
SELECT
|
34
34
|
schemaname AS schema,
|
35
35
|
relname AS table,
|
@@ -47,7 +47,7 @@ module PgHero
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def missing_indexes
|
50
|
-
select_all
|
50
|
+
select_all <<~SQL
|
51
51
|
SELECT
|
52
52
|
schemaname AS schema,
|
53
53
|
relname AS table,
|
@@ -69,7 +69,7 @@ module PgHero
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def unused_indexes(max_scans: 50, across: [])
|
72
|
-
result = select_all_size
|
72
|
+
result = select_all_size <<~SQL
|
73
73
|
SELECT
|
74
74
|
schemaname AS schema,
|
75
75
|
relname AS table,
|
@@ -104,7 +104,7 @@ module PgHero
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def last_stats_reset_time
|
107
|
-
select_one
|
107
|
+
select_one <<~SQL
|
108
108
|
SELECT
|
109
109
|
pg_stat_get_db_stat_reset_time(oid) AS reset_time
|
110
110
|
FROM
|
@@ -126,7 +126,7 @@ module PgHero
|
|
126
126
|
# TODO parse array properly
|
127
127
|
# https://stackoverflow.com/questions/2204058/list-columns-with-indexes-in-postgresql
|
128
128
|
def indexes
|
129
|
-
indexes = select_all(
|
129
|
+
indexes = select_all(<<~SQL
|
130
130
|
SELECT
|
131
131
|
schemaname AS schema,
|
132
132
|
t.relname AS table,
|
@@ -186,7 +186,7 @@ module PgHero
|
|
186
186
|
# thanks @jberkus and @mbanck
|
187
187
|
def index_bloat(min_size: nil)
|
188
188
|
min_size ||= index_bloat_bytes
|
189
|
-
select_all
|
189
|
+
select_all <<~SQL
|
190
190
|
WITH btree_index_atts AS (
|
191
191
|
SELECT
|
192
192
|
nspname, relname, reltuples, relpages, indrelid, relam,
|
data/lib/pghero/methods/kill.rb
CHANGED
@@ -9,7 +9,7 @@ module PgHero
|
|
9
9
|
max_value = max_value.to_i
|
10
10
|
threshold = threshold.to_i
|
11
11
|
|
12
|
-
select_all
|
12
|
+
select_all <<~SQL
|
13
13
|
SELECT
|
14
14
|
n.nspname AS schema,
|
15
15
|
c.relname AS table,
|
@@ -35,7 +35,7 @@ module PgHero
|
|
35
35
|
|
36
36
|
def vacuum_progress
|
37
37
|
if server_version_num >= 90600
|
38
|
-
select_all
|
38
|
+
select_all <<~SQL
|
39
39
|
SELECT
|
40
40
|
pid,
|
41
41
|
phase
|
@@ -50,7 +50,7 @@ module PgHero
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def maintenance_info
|
53
|
-
select_all
|
53
|
+
select_all <<~SQL
|
54
54
|
SELECT
|
55
55
|
schemaname AS schema,
|
56
56
|
relname AS table,
|
@@ -2,7 +2,7 @@ module PgHero
|
|
2
2
|
module Methods
|
3
3
|
module Queries
|
4
4
|
def running_queries(min_duration: nil, all: false)
|
5
|
-
query =
|
5
|
+
query = <<~SQL
|
6
6
|
SELECT
|
7
7
|
pid,
|
8
8
|
state,
|
@@ -36,7 +36,7 @@ module PgHero
|
|
36
36
|
# from https://wiki.postgresql.org/wiki/Lock_Monitoring
|
37
37
|
# and https://big-elephants.com/2013-09/exploring-query-locks-in-postgres/
|
38
38
|
def blocked_queries
|
39
|
-
query =
|
39
|
+
query = <<~SQL
|
40
40
|
SELECT
|
41
41
|
COALESCE(blockingl.relation::regclass::text,blockingl.locktype) as locked_item,
|
42
42
|
blockeda.pid AS blocked_pid,
|
@@ -162,19 +162,20 @@ module PgHero
|
|
162
162
|
end
|
163
163
|
end
|
164
164
|
|
165
|
-
def clean_query_stats
|
166
|
-
|
165
|
+
def clean_query_stats(before: nil)
|
166
|
+
before ||= 14.days.ago
|
167
|
+
PgHero::QueryStats.where(database: id).where("captured_at < ?", before).delete_all
|
167
168
|
end
|
168
169
|
|
169
170
|
def slow_queries(query_stats: nil, **options)
|
170
|
-
query_stats ||= self.query_stats(options)
|
171
|
+
query_stats ||= self.query_stats(**options)
|
171
172
|
query_stats.select { |q| q[:calls].to_i >= slow_query_calls.to_i && q[:average_time].to_f >= slow_query_ms.to_f }
|
172
173
|
end
|
173
174
|
|
174
|
-
def query_hash_stats(query_hash, user: nil)
|
175
|
+
def query_hash_stats(query_hash, user: nil, current: false)
|
175
176
|
if historical_query_stats_enabled? && supports_query_hash?
|
176
177
|
start_at = 24.hours.ago
|
177
|
-
select_all_stats
|
178
|
+
stats = select_all_stats <<~SQL
|
178
179
|
SELECT
|
179
180
|
captured_at,
|
180
181
|
total_time / 1000 / 60 AS total_minutes,
|
@@ -191,6 +192,15 @@ module PgHero
|
|
191
192
|
ORDER BY
|
192
193
|
1 ASC
|
193
194
|
SQL
|
195
|
+
if current
|
196
|
+
captured_at = Time.current
|
197
|
+
current_stats = current_query_stats(query_hash: query_hash, user: user, origin: true)
|
198
|
+
current_stats.each do |r|
|
199
|
+
r[:captured_at] = captured_at
|
200
|
+
end
|
201
|
+
stats += current_stats
|
202
|
+
end
|
203
|
+
stats
|
194
204
|
else
|
195
205
|
raise NotEnabled, "Query hash stats not enabled"
|
196
206
|
end
|
@@ -199,12 +209,12 @@ module PgHero
|
|
199
209
|
private
|
200
210
|
|
201
211
|
# http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/
|
202
|
-
def current_query_stats(limit: nil, sort: nil, database: nil, query_hash: nil)
|
212
|
+
def current_query_stats(limit: nil, sort: nil, database: nil, query_hash: nil, user: nil, origin: false)
|
203
213
|
if query_stats_enabled?
|
204
214
|
limit ||= 100
|
205
215
|
sort ||= "total_minutes"
|
206
216
|
total_time = server_version_num >= 130000 ? "(total_plan_time + total_exec_time)" : "total_time"
|
207
|
-
query =
|
217
|
+
query = <<~SQL
|
208
218
|
WITH query_stats AS (
|
209
219
|
SELECT
|
210
220
|
LEFT(query, 10000) AS query,
|
@@ -223,10 +233,12 @@ module PgHero
|
|
223
233
|
calls > 0 AND
|
224
234
|
pg_database.datname = #{database ? quote(database) : "current_database()"}
|
225
235
|
#{query_hash ? "AND queryid = #{quote(query_hash)}" : nil}
|
236
|
+
#{user ? "AND rolname = #{quote(user)}" : nil}
|
226
237
|
)
|
227
238
|
SELECT
|
228
239
|
query,
|
229
240
|
query AS explainable_query,
|
241
|
+
#{origin ? "(SELECT regexp_matches(query, '.*/\\*(.+?)\\*/'))[1] AS origin," : nil}
|
230
242
|
query_hash,
|
231
243
|
query_stats.user,
|
232
244
|
total_minutes,
|
@@ -237,7 +249,7 @@ module PgHero
|
|
237
249
|
FROM
|
238
250
|
query_stats
|
239
251
|
ORDER BY
|
240
|
-
#{
|
252
|
+
#{quote_column_name(sort)} DESC
|
241
253
|
LIMIT #{limit.to_i}
|
242
254
|
SQL
|
243
255
|
|
@@ -253,7 +265,7 @@ module PgHero
|
|
253
265
|
def historical_query_stats(sort: nil, start_at: nil, end_at: nil, query_hash: nil)
|
254
266
|
if historical_query_stats_enabled?
|
255
267
|
sort ||= "total_minutes"
|
256
|
-
query =
|
268
|
+
query = <<~SQL
|
257
269
|
WITH query_stats AS (
|
258
270
|
SELECT
|
259
271
|
#{supports_query_hash? ? "query_hash" : "md5(query)"} AS query_hash,
|
@@ -286,7 +298,7 @@ module PgHero
|
|
286
298
|
FROM
|
287
299
|
query_stats
|
288
300
|
ORDER BY
|
289
|
-
#{
|
301
|
+
#{quote_column_name(sort)} DESC
|
290
302
|
LIMIT 100
|
291
303
|
SQL
|
292
304
|
|
@@ -310,14 +322,14 @@ module PgHero
|
|
310
322
|
all_queries_total_minutes: stats2.sum { |s| s[:all_queries_total_minutes] }
|
311
323
|
}
|
312
324
|
value[:total_percent] = value[:total_minutes] * 100.0 / value[:all_queries_total_minutes]
|
313
|
-
value[:explainable_query] = stats2.map { |s| s[:explainable_query] }.
|
325
|
+
value[:explainable_query] = stats2.map { |s| s[:explainable_query] }.find { |q| q && explainable?(q) }
|
314
326
|
query_stats << value
|
315
327
|
end
|
316
328
|
query_stats
|
317
329
|
end
|
318
330
|
|
319
331
|
def explainable?(query)
|
320
|
-
query =~ /select/i && !query.include?("?)") && !query.include?("= ?") && !query.include?("$1") && query !~ /limit \?/i
|
332
|
+
query =~ /select/i && (server_version_num >= 160000 || (!query.include?("?)") && !query.include?("= ?") && !query.include?("$1") && query !~ /limit \?/i))
|
321
333
|
end
|
322
334
|
|
323
335
|
# removes comments
|
@@ -329,19 +341,17 @@ module PgHero
|
|
329
341
|
def insert_query_stats(db_id, db_query_stats, now)
|
330
342
|
values =
|
331
343
|
db_query_stats.map do |qs|
|
332
|
-
|
333
|
-
db_id,
|
334
|
-
qs[:query],
|
335
|
-
qs[:total_minutes] * 60 * 1000,
|
336
|
-
qs[:calls],
|
337
|
-
now,
|
338
|
-
supports_query_hash? ? qs[:query_hash] : nil,
|
339
|
-
qs[:user]
|
340
|
-
|
344
|
+
{
|
345
|
+
database: db_id,
|
346
|
+
query: qs[:query],
|
347
|
+
total_time: qs[:total_minutes] * 60 * 1000,
|
348
|
+
calls: qs[:calls],
|
349
|
+
captured_at: now,
|
350
|
+
query_hash: supports_query_hash? ? qs[:query_hash] : nil,
|
351
|
+
user: qs[:user]
|
352
|
+
}
|
341
353
|
end
|
342
|
-
|
343
|
-
columns = %w[database query total_time calls captured_at query_hash user]
|
344
|
-
insert_stats("pghero_query_stats", columns, values)
|
354
|
+
PgHero::QueryStats.insert_all!(values)
|
345
355
|
end
|
346
356
|
end
|
347
357
|
end
|
@@ -18,7 +18,7 @@ module PgHero
|
|
18
18
|
"pg_last_xlog_receive_location() = pg_last_xlog_replay_location()"
|
19
19
|
end
|
20
20
|
|
21
|
-
select_one
|
21
|
+
select_one <<~SQL
|
22
22
|
SELECT
|
23
23
|
CASE
|
24
24
|
WHEN NOT pg_is_in_recovery() OR #{lag_condition} THEN 0
|
@@ -32,7 +32,7 @@ module PgHero
|
|
32
32
|
def replication_slots
|
33
33
|
if server_version_num >= 90400
|
34
34
|
with_feature_support(:replication_slots, []) do
|
35
|
-
select_all
|
35
|
+
select_all <<~SQL
|
36
36
|
SELECT
|
37
37
|
slot_name,
|
38
38
|
database,
|
@@ -7,7 +7,7 @@ module PgHero
|
|
7
7
|
# it's what information_schema.columns uses
|
8
8
|
# also, exclude temporary tables to prevent error
|
9
9
|
# when accessing across sessions
|
10
|
-
sequences = select_all
|
10
|
+
sequences = select_all <<~SQL
|
11
11
|
SELECT
|
12
12
|
n.nspname AS table_schema,
|
13
13
|
c.relname AS table,
|
@@ -39,14 +39,19 @@ module PgHero
|
|
39
39
|
|
40
40
|
add_sequence_attributes(sequences)
|
41
41
|
|
42
|
-
|
43
|
-
|
42
|
+
last_value = {}
|
43
|
+
sequences.select { |s| s[:readable] }.map { |s| [s[:schema], s[:sequence]] }.uniq.each_slice(1024) do |slice|
|
44
|
+
sql = slice.map { |s| "SELECT last_value FROM #{quote_ident(s[0])}.#{quote_ident(s[1])}" }.join(" UNION ALL ")
|
44
45
|
|
45
46
|
select_all(sql).zip(slice) do |row, seq|
|
46
|
-
seq
|
47
|
+
last_value[seq] = row[:last_value]
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
51
|
+
sequences.select { |s| s[:readable] }.each do |seq|
|
52
|
+
seq[:last_value] = last_value[[seq[:schema], seq[:sequence]]]
|
53
|
+
end
|
54
|
+
|
50
55
|
# use to_s for unparsable sequences
|
51
56
|
sequences.sort_by { |s| s[:sequence].to_s }
|
52
57
|
end
|
@@ -84,7 +89,7 @@ module PgHero
|
|
84
89
|
# also adds schema if missing
|
85
90
|
def add_sequence_attributes(sequences)
|
86
91
|
# fetch data
|
87
|
-
sequence_attributes = select_all
|
92
|
+
sequence_attributes = select_all <<~SQL
|
88
93
|
SELECT
|
89
94
|
n.nspname AS schema,
|
90
95
|
c.relname AS sequence,
|
@@ -3,7 +3,14 @@ module PgHero
|
|
3
3
|
module Settings
|
4
4
|
def settings
|
5
5
|
names =
|
6
|
-
if server_version_num >=
|
6
|
+
if server_version_num >= 100000
|
7
|
+
%i(
|
8
|
+
max_connections shared_buffers effective_cache_size maintenance_work_mem
|
9
|
+
checkpoint_completion_target wal_buffers default_statistics_target
|
10
|
+
random_page_cost effective_io_concurrency work_mem huge_pages
|
11
|
+
min_wal_size max_wal_size
|
12
|
+
)
|
13
|
+
elsif server_version_num >= 90500
|
7
14
|
%i(
|
8
15
|
max_connections shared_buffers effective_cache_size work_mem
|
9
16
|
maintenance_work_mem min_wal_size max_wal_size checkpoint_completion_target
|
data/lib/pghero/methods/space.rb
CHANGED
@@ -6,11 +6,11 @@ module PgHero
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def relation_sizes
|
9
|
-
select_all_size
|
9
|
+
select_all_size <<~SQL
|
10
10
|
SELECT
|
11
11
|
n.nspname AS schema,
|
12
12
|
c.relname AS relation,
|
13
|
-
CASE
|
13
|
+
CASE c.relkind WHEN 'r' THEN 'table' WHEN 'm' then 'matview' ELSE 'index' END AS type,
|
14
14
|
pg_table_size(c.oid) AS size_bytes
|
15
15
|
FROM
|
16
16
|
pg_class c
|
@@ -19,7 +19,7 @@ module PgHero
|
|
19
19
|
WHERE
|
20
20
|
n.nspname NOT IN ('pg_catalog', 'information_schema')
|
21
21
|
AND n.nspname !~ '^pg_toast'
|
22
|
-
AND c.relkind IN ('r', 'i')
|
22
|
+
AND c.relkind IN ('r', 'm', 'i')
|
23
23
|
ORDER BY
|
24
24
|
pg_table_size(c.oid) DESC,
|
25
25
|
2 ASC
|
@@ -27,7 +27,7 @@ module PgHero
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def table_sizes
|
30
|
-
select_all_size
|
30
|
+
select_all_size <<~SQL
|
31
31
|
SELECT
|
32
32
|
n.nspname AS schema,
|
33
33
|
c.relname AS table,
|
@@ -52,7 +52,7 @@ module PgHero
|
|
52
52
|
sizes = relation_sizes.to_h { |r| [[r[:schema], r[:relation]], r[:size_bytes]] }
|
53
53
|
start_at = days.days.ago
|
54
54
|
|
55
|
-
stats = select_all_stats
|
55
|
+
stats = select_all_stats <<~SQL
|
56
56
|
WITH t AS (
|
57
57
|
SELECT
|
58
58
|
schema,
|
@@ -95,7 +95,7 @@ module PgHero
|
|
95
95
|
sizes = relation_sizes.map { |r| [[r[:schema], r[:relation]], r[:size_bytes]] }.to_h
|
96
96
|
start_at = 30.days.ago
|
97
97
|
|
98
|
-
stats = select_all_stats
|
98
|
+
stats = select_all_stats <<~SQL
|
99
99
|
SELECT
|
100
100
|
captured_at,
|
101
101
|
size AS size_bytes
|
@@ -121,16 +121,22 @@ module PgHero
|
|
121
121
|
|
122
122
|
def capture_space_stats
|
123
123
|
now = Time.now
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
124
|
+
values =
|
125
|
+
relation_sizes.map do |rs|
|
126
|
+
{
|
127
|
+
database: id,
|
128
|
+
schema: rs[:schema],
|
129
|
+
relation: rs[:relation],
|
130
|
+
size: rs[:size_bytes].to_i,
|
131
|
+
captured_at: now
|
132
|
+
}
|
133
|
+
end
|
134
|
+
PgHero::SpaceStats.insert_all!(values) if values.any?
|
130
135
|
end
|
131
136
|
|
132
|
-
def clean_space_stats
|
133
|
-
|
137
|
+
def clean_space_stats(before: nil)
|
138
|
+
before ||= 90.days.ago
|
139
|
+
PgHero::SpaceStats.where(database: id).where("captured_at < ?", before).delete_all
|
134
140
|
end
|
135
141
|
|
136
142
|
def space_stats_enabled?
|
@@ -46,7 +46,7 @@ module PgHero
|
|
46
46
|
indexes += existing_columns["brin"][index[:table]]
|
47
47
|
end
|
48
48
|
|
49
|
-
covering_index = indexes.find { |e| index_covers?(e.map { |v| v.
|
49
|
+
covering_index = indexes.find { |e| index_covers?(e.map { |v| v.delete_suffix(" inet_ops") }, index[:columns]) }
|
50
50
|
if covering_index
|
51
51
|
best_index[:covering_index] = covering_index
|
52
52
|
best_index[:explanation] = "Covered by index on (#{covering_index.join(", ")})"
|
@@ -79,7 +79,9 @@ module PgHero
|
|
79
79
|
suggested_indexes.each do |index|
|
80
80
|
p index
|
81
81
|
if create
|
82
|
-
|
82
|
+
with_connection do |connection|
|
83
|
+
connection.execute("CREATE INDEX CONCURRENTLY ON #{quote_table_name(index[:table])} (#{index[:columns].map { |c| quote_column_name(c) }.join(",")})")
|
84
|
+
end
|
83
85
|
end
|
84
86
|
end
|
85
87
|
end
|
@@ -194,6 +196,7 @@ module PgHero
|
|
194
196
|
end
|
195
197
|
|
196
198
|
def best_index_structure(statement)
|
199
|
+
return {error: "Empty statement"} if statement.to_s.empty?
|
197
200
|
return {error: "Too large"} if statement.to_s.length > 10000
|
198
201
|
|
199
202
|
begin
|
@@ -286,27 +289,31 @@ module PgHero
|
|
286
289
|
else
|
287
290
|
raise "Not Implemented"
|
288
291
|
end
|
289
|
-
elsif aexpr && ["=", "<>", ">", ">=", "<", "<=", "~~", "~~*", "BETWEEN"].include?(aexpr.name.first.string.
|
290
|
-
[{column: aexpr.lexpr.column_ref.fields.last.string.
|
292
|
+
elsif aexpr && ["=", "<>", ">", ">=", "<", "<=", "~~", "~~*", "BETWEEN"].include?(aexpr.name.first.string.send(str_method))
|
293
|
+
[{column: aexpr.lexpr.column_ref.fields.last.string.send(str_method), op: aexpr.name.first.string.send(str_method)}]
|
291
294
|
elsif tree.null_test
|
292
295
|
op = tree.null_test.nulltesttype == :IS_NOT_NULL ? "not_null" : "null"
|
293
|
-
[{column: tree.null_test.arg.column_ref.fields.last.string.
|
296
|
+
[{column: tree.null_test.arg.column_ref.fields.last.string.send(str_method), op: op}]
|
294
297
|
else
|
295
298
|
raise "Not Implemented"
|
296
299
|
end
|
297
300
|
end
|
298
301
|
|
302
|
+
def str_method
|
303
|
+
@str_method ||= Gem::Version.new(PgQuery::VERSION) >= Gem::Version.new("4") ? :sval : :str
|
304
|
+
end
|
305
|
+
|
299
306
|
def parse_sort(sort_clause)
|
300
307
|
sort_clause.map do |v|
|
301
308
|
{
|
302
|
-
column: v.sort_by.node.column_ref.fields.last.string.
|
309
|
+
column: v.sort_by.node.column_ref.fields.last.string.send(str_method),
|
303
310
|
direction: v.sort_by.sortby_dir == :SORTBY_DESC ? "desc" : "asc"
|
304
311
|
}
|
305
312
|
end
|
306
313
|
end
|
307
314
|
|
308
315
|
def column_stats(schema: nil, table: nil)
|
309
|
-
select_all
|
316
|
+
select_all <<~SQL
|
310
317
|
SELECT
|
311
318
|
schemaname AS schema,
|
312
319
|
tablename AS table,
|