pghero 2.3.0 → 2.5.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -54
- data/README.md +20 -8
- data/app/assets/javascripts/pghero/Chart.bundle.js +16260 -15580
- data/app/assets/javascripts/pghero/application.js +8 -7
- data/app/assets/javascripts/pghero/chartkick.js +1973 -1325
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -2
- data/app/assets/javascripts/pghero/jquery.js +3605 -4015
- data/app/assets/javascripts/pghero/nouislider.js +2479 -0
- data/app/assets/stylesheets/pghero/application.css +1 -1
- data/app/assets/stylesheets/pghero/nouislider.css +299 -0
- data/app/controllers/pg_hero/home_controller.rb +94 -35
- data/app/helpers/pg_hero/home_helper.rb +11 -0
- data/app/views/pg_hero/home/_live_queries_table.html.erb +14 -2
- data/app/views/pg_hero/home/connections.html.erb +9 -0
- data/app/views/pg_hero/home/index.html.erb +49 -10
- data/app/views/pg_hero/home/live_queries.html.erb +1 -1
- data/app/views/pg_hero/home/maintenance.html.erb +16 -2
- data/app/views/pg_hero/home/relation_space.html.erb +2 -2
- data/app/views/pg_hero/home/show_query.html.erb +3 -3
- data/app/views/pg_hero/home/space.html.erb +3 -3
- data/app/views/pg_hero/home/system.html.erb +4 -4
- data/app/views/pg_hero/home/tune.html.erb +2 -1
- data/lib/generators/pghero/templates/config.yml.tt +21 -1
- data/lib/pghero.rb +63 -15
- data/lib/pghero/database.rb +101 -17
- data/lib/pghero/methods/basic.rb +28 -7
- data/lib/pghero/methods/connections.rb +35 -0
- data/lib/pghero/methods/constraints.rb +30 -0
- data/lib/pghero/methods/indexes.rb +1 -1
- data/lib/pghero/methods/maintenance.rb +3 -1
- data/lib/pghero/methods/queries.rb +6 -2
- data/lib/pghero/methods/query_stats.rb +12 -3
- data/lib/pghero/methods/suggested_indexes.rb +1 -1
- data/lib/pghero/methods/system.rb +219 -23
- data/lib/pghero/stats.rb +1 -1
- data/lib/pghero/version.rb +1 -1
- metadata +6 -5
- data/app/assets/javascripts/pghero/jquery.nouislider.min.js +0 -31
- data/app/assets/stylesheets/pghero/jquery.nouislider.css +0 -165
@@ -15,5 +15,16 @@ module PgHero
|
|
15
15
|
def pghero_js_var(name, value)
|
16
16
|
"var #{name} = #{json_escape(value.to_json(root: false))};".html_safe
|
17
17
|
end
|
18
|
+
|
19
|
+
def pghero_remove_index(query)
|
20
|
+
if query[:columns]
|
21
|
+
columns = query[:columns].map(&:to_sym)
|
22
|
+
columns = columns.first if columns.size == 1
|
23
|
+
end
|
24
|
+
ret = String.new("remove_index #{query[:table].to_sym.inspect}")
|
25
|
+
ret << ", name: #{(query[:name] || query[:index]).to_s.inspect}"
|
26
|
+
ret << ", column: #{columns.inspect}" if columns
|
27
|
+
ret
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
@@ -11,7 +11,17 @@
|
|
11
11
|
<% queries.reverse.each do |query| %>
|
12
12
|
<tr>
|
13
13
|
<td><%= query[:pid] %></td>
|
14
|
-
<td
|
14
|
+
<td>
|
15
|
+
<% sec = query[:duration_ms] / 1000.0 %>
|
16
|
+
<% if sec < 1.minute %>
|
17
|
+
<%= sec.round(1) %> s
|
18
|
+
<% elsif sec < 1.day %>
|
19
|
+
<%= Time.at(sec).utc.strftime("%H:%M:%S") %>
|
20
|
+
<% else %>
|
21
|
+
<% days = (sec / 1.day).floor %>
|
22
|
+
<%= days %>d <%= Time.at(sec - days.days).utc.strftime("%H:%M:%S") %>
|
23
|
+
<% end %>
|
24
|
+
</td>
|
15
25
|
<td>
|
16
26
|
<%= query[:state] %>
|
17
27
|
<% if vacuum_progress[query[:pid]] %>
|
@@ -20,7 +30,9 @@
|
|
20
30
|
<% end %>
|
21
31
|
</td>
|
22
32
|
<td class="text-right">
|
23
|
-
|
33
|
+
<% unless @database.filter_data %>
|
34
|
+
<%= button_to "Explain", explain_path, params: {query: query[:query]}, form: {target: "_blank"}, class: "btn btn-info" %>
|
35
|
+
<% end %>
|
24
36
|
<%= button_to "Kill", kill_path(pid: query[:pid]), class: "btn btn-danger" %>
|
25
37
|
</td>
|
26
38
|
</tr>
|
@@ -18,6 +18,15 @@
|
|
18
18
|
new Chartkick.PieChart("chart-2", <%= json_escape(@connections_by_user.to_json).html_safe %>);
|
19
19
|
</script>
|
20
20
|
|
21
|
+
<% if @connections_by_ssl_status %>
|
22
|
+
<h3>By Security</h3>
|
23
|
+
|
24
|
+
<div id="chart-3" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
25
|
+
<script>
|
26
|
+
new Chartkick.PieChart("chart-3", <%= json_escape(@connections_by_ssl_status.to_json).html_safe %>);
|
27
|
+
</script>
|
28
|
+
<% end %>
|
29
|
+
|
21
30
|
<%= render partial: "connections_table", locals: {connection_sources: @connection_sources} %>
|
22
31
|
<% end %>
|
23
32
|
</div>
|
@@ -62,11 +62,17 @@
|
|
62
62
|
(<%= link_to pluralize(@unreadable_sequences.size, "unreadable sequence", "unreadable sequences"), {unreadable: "t"} %>)
|
63
63
|
<% end %>
|
64
64
|
</div>
|
65
|
-
<div class="alert alert-<%= @invalid_indexes.empty? ? "success" : "warning" %>">
|
66
|
-
<% if @invalid_indexes.
|
67
|
-
|
65
|
+
<div class="alert alert-<%= @invalid_indexes.empty? && @invalid_constraints.empty? ? "success" : "warning" %>">
|
66
|
+
<% if @invalid_indexes.empty? && @invalid_constraints.empty? %>
|
67
|
+
No invalid indexes or constraints
|
68
68
|
<% else %>
|
69
|
-
|
69
|
+
<% if @invalid_indexes.any? %>
|
70
|
+
<%= pluralize(@invalid_indexes.size, "invalid index", "invalid indexes") %>
|
71
|
+
<% end %>
|
72
|
+
<% if @invalid_constraints.any? %>
|
73
|
+
<% if @invalid_indexes.any? %>and<% end %>
|
74
|
+
<%= pluralize(@invalid_constraints.size, "invalid constraint", "invalid constraints") %>
|
75
|
+
<% end %>
|
70
76
|
<% end %>
|
71
77
|
</div>
|
72
78
|
<% if @duplicate_indexes %>
|
@@ -332,6 +338,39 @@
|
|
332
338
|
</div>
|
333
339
|
<% end %>
|
334
340
|
|
341
|
+
<% if @invalid_constraints.any? %>
|
342
|
+
<div class="content">
|
343
|
+
<h1>Invalid Constraints</h1>
|
344
|
+
|
345
|
+
<p>These constraints are marked as <code>NOT VALID</code>. You should validate them.</p>
|
346
|
+
|
347
|
+
<table class="table">
|
348
|
+
<thead>
|
349
|
+
<tr>
|
350
|
+
<th>Name</th>
|
351
|
+
</tr>
|
352
|
+
</thead>
|
353
|
+
<tbody>
|
354
|
+
<% @invalid_constraints.each do |constraint| %>
|
355
|
+
<tr>
|
356
|
+
<td>
|
357
|
+
<%= constraint[:name] %>
|
358
|
+
<% if constraint[:schema] != "public" %>
|
359
|
+
<span class="text-muted"><%= constraint[:schema] %></span>
|
360
|
+
<% end %>
|
361
|
+
</td>
|
362
|
+
</tr>
|
363
|
+
<tr>
|
364
|
+
<td style="border-top: none; padding: 0;">
|
365
|
+
<pre><code>ALTER TABLE <%= pghero_pretty_ident(constraint[:table], schema: constraint[:schema]) %> VALIDATE CONSTRAINT <%= pghero_pretty_ident(constraint[:name]) %>;</code></pre>
|
366
|
+
</td>
|
367
|
+
</tr>
|
368
|
+
<% end %>
|
369
|
+
</tbody>
|
370
|
+
</table>
|
371
|
+
</div>
|
372
|
+
<% end %>
|
373
|
+
|
335
374
|
<% if @duplicate_indexes && @duplicate_indexes.any? %>
|
336
375
|
<div class="content">
|
337
376
|
<h1>Duplicate Indexes</h1>
|
@@ -345,10 +384,10 @@
|
|
345
384
|
</p>
|
346
385
|
|
347
386
|
<div id="migration2" style="display: none;">
|
348
|
-
<pre>rails
|
387
|
+
<pre>rails generate migration remove_unneeded_indexes</pre>
|
349
388
|
<p>And paste</p>
|
350
389
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
|
351
|
-
|
390
|
+
<%= pghero_remove_index(query[:unneeded_index]) %><% end %></pre>
|
352
391
|
</div>
|
353
392
|
|
354
393
|
<table class="table duplicate-indexes">
|
@@ -387,12 +426,12 @@ remove_index <%= query[:unneeded_index][:table].to_sym.inspect %>, name: <%= que
|
|
387
426
|
</p>
|
388
427
|
|
389
428
|
<div id="migration3" style="display: none;">
|
390
|
-
<pre>rails
|
429
|
+
<pre>rails generate migration add_suggested_indexes</pre>
|
391
430
|
<p>And paste</p>
|
392
431
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;">commit_db_transaction
|
393
432
|
<% @suggested_indexes.each do |index| %>
|
394
433
|
<% if index[:using] && index[:using] != "btree" %>
|
395
|
-
|
434
|
+
add_index <%= index[:table].to_sym.inspect %>, <%= index[:columns].first.inspect %>, using: <%= index[:using].inspect %>, algorithm: :concurrently
|
396
435
|
<% else %>
|
397
436
|
add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(", ") %>], algorithm: :concurrently<% end %>
|
398
437
|
<% end %></pre>
|
@@ -449,10 +488,10 @@ pg_stat_statements.track = all
|
|
449
488
|
</p>
|
450
489
|
|
451
490
|
<div id="migration" style="display: none;">
|
452
|
-
<pre>rails
|
491
|
+
<pre>rails generate migration remove_unused_indexes</pre>
|
453
492
|
<p>And paste</p>
|
454
493
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.each do |query| %>
|
455
|
-
|
494
|
+
<%= pghero_remove_index(query)%><% end %></pre>
|
456
495
|
</div>
|
457
496
|
|
458
497
|
<table class="table">
|
@@ -7,6 +7,9 @@
|
|
7
7
|
<th>Table</th>
|
8
8
|
<th style="width: 20%;">Last Vacuum</th>
|
9
9
|
<th style="width: 20%;">Last Analyze</th>
|
10
|
+
<% if @show_dead_rows %>
|
11
|
+
<th style="width: 20%;">Dead Rows</th>
|
12
|
+
<% end %>
|
10
13
|
</tr>
|
11
14
|
</thead>
|
12
15
|
<tbody>
|
@@ -21,7 +24,7 @@
|
|
21
24
|
<td>
|
22
25
|
<% time = [table[:last_autovacuum], table[:last_vacuum]].compact.max %>
|
23
26
|
<% if time %>
|
24
|
-
<%= time.in_time_zone(@time_zone)
|
27
|
+
<%= l time.in_time_zone(@time_zone), format: :short %>
|
25
28
|
<% else %>
|
26
29
|
<span class="text-muted">Unknown</span>
|
27
30
|
<% end %>
|
@@ -29,11 +32,22 @@
|
|
29
32
|
<td>
|
30
33
|
<% time = [table[:last_autoanalyze], table[:last_analyze]].compact.max %>
|
31
34
|
<% if time %>
|
32
|
-
<%= time.in_time_zone(@time_zone)
|
35
|
+
<%= l time.in_time_zone(@time_zone), format: :short %>
|
33
36
|
<% else %>
|
34
37
|
<span class="text-muted">Unknown</span>
|
35
38
|
<% end %>
|
36
39
|
</td>
|
40
|
+
<% if @show_dead_rows %>
|
41
|
+
<td>
|
42
|
+
<% if table[:live_rows] != 0 %>
|
43
|
+
<%# use live rows only for denominator to make it easier to compare with autovacuum_vacuum_scale_factor %>
|
44
|
+
<%# it's not a true percentage, since it can go above 100% %>
|
45
|
+
<%= (100.0 * table[:dead_rows] / table[:live_rows]).round %>%
|
46
|
+
<% else %>
|
47
|
+
<span class="text-muted">Unknown</span>
|
48
|
+
<% end %>
|
49
|
+
</td>
|
50
|
+
<% end %>
|
37
51
|
</tr>
|
38
52
|
<% end %>
|
39
53
|
</tbody>
|
@@ -6,9 +6,9 @@
|
|
6
6
|
<% end %>
|
7
7
|
</h1>
|
8
8
|
|
9
|
-
<h1>Size
|
9
|
+
<h1>Size</h1>
|
10
10
|
<div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
11
11
|
<script>
|
12
|
-
new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, min: null})
|
12
|
+
new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, min: null, bytes: true, library: {tooltips: {intersect: false, mode: "index"}}})
|
13
13
|
</script>
|
14
14
|
</div>
|
@@ -49,19 +49,19 @@
|
|
49
49
|
<h1>Total Time <small>ms</small></h1>
|
50
50
|
<div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
51
51
|
<script>
|
52
|
-
new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
|
52
|
+
new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
|
53
53
|
</script>
|
54
54
|
|
55
55
|
<h1>Average Time <small>ms</small></h1>
|
56
56
|
<div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
57
57
|
<script>
|
58
|
-
new Chartkick.LineChart("chart-2", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
|
58
|
+
new Chartkick.LineChart("chart-2", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
|
59
59
|
</script>
|
60
60
|
|
61
61
|
<h1>Calls</h1>
|
62
62
|
<div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
63
63
|
<script>
|
64
|
-
new Chartkick.LineChart("chart-3", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
|
64
|
+
new Chartkick.LineChart("chart-3", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
|
65
65
|
</script>
|
66
66
|
<% else %>
|
67
67
|
<p>
|
@@ -6,7 +6,7 @@
|
|
6
6
|
<% if @system_stats_enabled %>
|
7
7
|
<div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
8
8
|
<script>
|
9
|
-
new Chartkick.LineChart("chart-1", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"]})
|
9
|
+
new Chartkick.LineChart("chart-1", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"], bytes: true, library: {tooltips: {intersect: false, mode: "index"}}})
|
10
10
|
</script>
|
11
11
|
<% end %>
|
12
12
|
|
@@ -30,10 +30,10 @@
|
|
30
30
|
</p>
|
31
31
|
|
32
32
|
<div id="migration" style="display: none;">
|
33
|
-
<pre>rails
|
33
|
+
<pre>rails generate migration remove_unused_indexes</pre>
|
34
34
|
<p>And paste</p>
|
35
35
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.sort_by { |q| [-q[:size_bytes], q[:index]] }.each do |query| %>
|
36
|
-
|
36
|
+
<%= pghero_remove_index(query) %><% end %></pre>
|
37
37
|
</div>
|
38
38
|
<% end %>
|
39
39
|
|
@@ -9,26 +9,26 @@
|
|
9
9
|
<h1>CPU</h1>
|
10
10
|
<div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
11
11
|
<script>
|
12
|
-
new Chartkick.LineChart("chart-1", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"]})
|
12
|
+
new Chartkick.LineChart("chart-1", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"], suffix: "%", library: {tooltips: {intersect: false, mode: "index"}}})
|
13
13
|
</script>
|
14
14
|
|
15
15
|
<h1>Load</h1>
|
16
16
|
<div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
17
17
|
<script>
|
18
|
-
new Chartkick.LineChart("chart-2", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"]})
|
18
|
+
new Chartkick.LineChart("chart-2", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"], library: {tooltips: {intersect: false, mode: "nearest"}}})
|
19
19
|
</script>
|
20
20
|
|
21
21
|
<h1>Connections</h1>
|
22
22
|
<div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
23
23
|
<script>
|
24
|
-
new Chartkick.LineChart("chart-3", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"]})
|
24
|
+
new Chartkick.LineChart("chart-3", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {tooltips: {intersect: false, mode: "index"}}})
|
25
25
|
</script>
|
26
26
|
|
27
27
|
<% if @database.replica? %>
|
28
28
|
<h1>Replication Lag</h1>
|
29
29
|
<div id="chart-4" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
30
30
|
<script>
|
31
|
-
new Chartkick.LineChart("chart-4", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"]})
|
31
|
+
new Chartkick.LineChart("chart-4", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {tooltips: {intersect: false, mode: "index"}}})
|
32
32
|
</script>
|
33
33
|
<% end %>
|
34
34
|
</div>
|
@@ -18,7 +18,8 @@
|
|
18
18
|
</tbody>
|
19
19
|
</table>
|
20
20
|
|
21
|
-
|
21
|
+
<% version_parts = @database.server_version.split(" ").first.split(".") %>
|
22
|
+
<p>Check out <%= link_to "PgTune", "https://pgtune.leopard.in.ua/", target: "_blank" %> for recommendations. DB version is <%= version_parts[0].to_i >= 10 ? version_parts[0] : version_parts.first(2).join(".") %>.</p>
|
22
23
|
</div>
|
23
24
|
|
24
25
|
<% if @autovacuum_settings %>
|
@@ -1,8 +1,13 @@
|
|
1
1
|
databases:
|
2
|
-
|
2
|
+
primary:
|
3
3
|
# Database URL (defaults to app database)
|
4
4
|
# url: <%%= ENV["DATABASE_URL"] %>
|
5
5
|
|
6
|
+
# System stats
|
7
|
+
# aws_db_instance_identifier: my-instance
|
8
|
+
# gcp_database_id: my-project:my-instance
|
9
|
+
# azure_resource_id: my-resource-id
|
10
|
+
|
6
11
|
# Add more databases
|
7
12
|
# other:
|
8
13
|
# url: <%%= ENV["OTHER_DATABASE_URL"] %>
|
@@ -24,3 +29,18 @@ databases:
|
|
24
29
|
|
25
30
|
# Time zone (defaults to app time zone)
|
26
31
|
# time_zone: "Pacific Time (US & Canada)"
|
32
|
+
|
33
|
+
# Basic authentication
|
34
|
+
# username: admin
|
35
|
+
# password: <%%= ENV["PGHERO_PASSWORD"] %>
|
36
|
+
|
37
|
+
# Stats database URL (defaults to app database)
|
38
|
+
# stats_database_url: <%%= ENV["PGHERO_STATS_DATABASE_URL"] %>
|
39
|
+
|
40
|
+
# AWS configuration (defaults to app AWS config)
|
41
|
+
# aws_access_key_id: <%%= ENV["AWS_ACCESS_KEY_ID"] %>
|
42
|
+
# aws_secret_access_key: <%%= ENV["AWS_SECRET_ACCESS_KEY"] %>
|
43
|
+
# aws_region: us-east-1
|
44
|
+
|
45
|
+
# Filter data from queries (experimental)
|
46
|
+
# filter_data: true
|
data/lib/pghero.rb
CHANGED
@@ -5,6 +5,7 @@ require "forwardable"
|
|
5
5
|
# methods
|
6
6
|
require "pghero/methods/basic"
|
7
7
|
require "pghero/methods/connections"
|
8
|
+
require "pghero/methods/constraints"
|
8
9
|
require "pghero/methods/explain"
|
9
10
|
require "pghero/methods/indexes"
|
10
11
|
require "pghero/methods/kill"
|
@@ -33,9 +34,11 @@ module PgHero
|
|
33
34
|
class Error < StandardError; end
|
34
35
|
class NotEnabled < Error; end
|
35
36
|
|
37
|
+
MUTEX = Mutex.new
|
38
|
+
|
36
39
|
# settings
|
37
40
|
class << self
|
38
|
-
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path
|
41
|
+
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path, :filter_data
|
39
42
|
end
|
40
43
|
self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
|
41
44
|
self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
|
@@ -46,14 +49,15 @@ module PgHero
|
|
46
49
|
self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
47
50
|
self.show_migrations = true
|
48
51
|
self.config_path = ENV["PGHERO_CONFIG_PATH"] || "config/pghero.yml"
|
52
|
+
self.filter_data = ENV["PGHERO_FILTER_DATA"].to_s.size > 0
|
49
53
|
|
50
54
|
class << self
|
51
55
|
extend Forwardable
|
52
56
|
def_delegators :primary_database, :access_key_id, :analyze, :analyze_tables, :autoindex, :autovacuum_danger,
|
53
|
-
:best_index, :blocked_queries, :connection_sources, :connection_states, :connection_stats,
|
57
|
+
:best_index, :blocked_queries, :connections, :connection_sources, :connection_states, :connection_stats,
|
54
58
|
:cpu_usage, :create_user, :database_size, :db_instance_identifier, :disable_query_stats, :drop_user,
|
55
59
|
:duplicate_indexes, :enable_query_stats, :explain, :historical_query_stats_enabled?, :index_caching,
|
56
|
-
:index_hit_rate, :index_usage, :indexes, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
|
60
|
+
:index_hit_rate, :index_usage, :indexes, :invalid_constraints, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
|
57
61
|
:last_stats_reset_time, :long_running_queries, :maintenance_info, :missing_indexes, :query_stats,
|
58
62
|
:query_stats_available?, :query_stats_enabled?, :query_stats_extension_enabled?, :query_stats_readable?,
|
59
63
|
:rds_stats, :read_iops_stats, :region, :relation_sizes, :replica?, :replication_lag, :replication_lag_stats,
|
@@ -70,6 +74,22 @@ module PgHero
|
|
70
74
|
@time_zone || Time.zone
|
71
75
|
end
|
72
76
|
|
77
|
+
# use method instead of attr_accessor to ensure
|
78
|
+
# this works if variable set after PgHero is loaded
|
79
|
+
def username
|
80
|
+
@username ||= config["username"] || ENV["PGHERO_USERNAME"]
|
81
|
+
end
|
82
|
+
|
83
|
+
# use method instead of attr_accessor to ensure
|
84
|
+
# this works if variable set after PgHero is loaded
|
85
|
+
def password
|
86
|
+
@password ||= config["password"] || ENV["PGHERO_PASSWORD"]
|
87
|
+
end
|
88
|
+
|
89
|
+
def stats_database_url
|
90
|
+
@stats_database_url ||= config["stats_database_url"] || ENV["PGHERO_STATS_DATABASE_URL"]
|
91
|
+
end
|
92
|
+
|
73
93
|
def config
|
74
94
|
@config ||= begin
|
75
95
|
require "erb"
|
@@ -89,26 +109,49 @@ module PgHero
|
|
89
109
|
elsif config_file_exists
|
90
110
|
raise "Invalid config file"
|
91
111
|
else
|
92
|
-
{
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
112
|
+
databases = {}
|
113
|
+
|
114
|
+
if !ENV["PGHERO_DATABASE_URL"] && spec_supported?
|
115
|
+
ActiveRecord::Base.configurations.configs_for(env_name: env, include_replicas: true).each do |db|
|
116
|
+
databases[db.spec_name] = {"spec" => db.spec_name}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if databases.empty?
|
121
|
+
databases["primary"] = {
|
122
|
+
"url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config
|
98
123
|
}
|
124
|
+
end
|
125
|
+
|
126
|
+
if databases.size == 1
|
127
|
+
databases.values.first.merge!(
|
128
|
+
"db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"],
|
129
|
+
"gcp_database_id" => ENV["PGHERO_GCP_DATABASE_ID"],
|
130
|
+
"azure_resource_id" => ENV["PGHERO_AZURE_RESOURCE_ID"]
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
{
|
135
|
+
"databases" => databases
|
99
136
|
}
|
100
137
|
end
|
101
138
|
end
|
102
139
|
end
|
103
140
|
|
141
|
+
# ensure we only have one copy of databases
|
142
|
+
# so there's only one connection pool per database
|
104
143
|
def databases
|
105
|
-
@databases
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
144
|
+
unless defined?(@databases)
|
145
|
+
# only use mutex on initialization
|
146
|
+
MUTEX.synchronize do
|
147
|
+
# return if another process initialized while we were waiting
|
148
|
+
return @databases if defined?(@databases)
|
149
|
+
|
150
|
+
@databases = config["databases"].map { |id, c| [id.to_sym, Database.new(id, c)] }.to_h
|
151
|
+
end
|
111
152
|
end
|
153
|
+
|
154
|
+
@databases
|
112
155
|
end
|
113
156
|
|
114
157
|
def primary_database
|
@@ -163,6 +206,11 @@ module PgHero
|
|
163
206
|
end
|
164
207
|
end
|
165
208
|
|
209
|
+
# private
|
210
|
+
def spec_supported?
|
211
|
+
ActiveRecord::VERSION::MAJOR >= 6
|
212
|
+
end
|
213
|
+
|
166
214
|
private
|
167
215
|
|
168
216
|
def each_database
|