pghero_fork 2.7.3
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 +7 -0
- data/CHANGELOG.md +391 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +3 -0
- data/app/assets/images/pghero/favicon.png +0 -0
- data/app/assets/javascripts/pghero/Chart.bundle.js +20755 -0
- data/app/assets/javascripts/pghero/application.js +158 -0
- data/app/assets/javascripts/pghero/chartkick.js +2436 -0
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
- data/app/assets/javascripts/pghero/jquery.js +10872 -0
- data/app/assets/javascripts/pghero/nouislider.js +2672 -0
- data/app/assets/stylesheets/pghero/application.css +514 -0
- data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
- data/app/assets/stylesheets/pghero/nouislider.css +310 -0
- data/app/controllers/pg_hero/home_controller.rb +449 -0
- data/app/helpers/pg_hero/home_helper.rb +30 -0
- data/app/views/layouts/pg_hero/application.html.erb +68 -0
- data/app/views/pg_hero/home/_connections_table.html.erb +16 -0
- data/app/views/pg_hero/home/_live_queries_table.html.erb +51 -0
- data/app/views/pg_hero/home/_queries_table.html.erb +72 -0
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +16 -0
- data/app/views/pg_hero/home/_suggested_index.html.erb +18 -0
- data/app/views/pg_hero/home/connections.html.erb +32 -0
- data/app/views/pg_hero/home/explain.html.erb +27 -0
- data/app/views/pg_hero/home/index.html.erb +518 -0
- data/app/views/pg_hero/home/index_bloat.html.erb +72 -0
- data/app/views/pg_hero/home/live_queries.html.erb +11 -0
- data/app/views/pg_hero/home/maintenance.html.erb +55 -0
- data/app/views/pg_hero/home/queries.html.erb +33 -0
- data/app/views/pg_hero/home/relation_space.html.erb +14 -0
- data/app/views/pg_hero/home/show_query.html.erb +106 -0
- data/app/views/pg_hero/home/space.html.erb +83 -0
- data/app/views/pg_hero/home/system.html.erb +34 -0
- data/app/views/pg_hero/home/tune.html.erb +53 -0
- data/config/routes.rb +32 -0
- data/lib/generators/pghero/config_generator.rb +13 -0
- data/lib/generators/pghero/query_stats_generator.rb +18 -0
- data/lib/generators/pghero/space_stats_generator.rb +18 -0
- data/lib/generators/pghero/templates/config.yml.tt +46 -0
- data/lib/generators/pghero/templates/query_stats.rb.tt +15 -0
- data/lib/generators/pghero/templates/space_stats.rb.tt +13 -0
- data/lib/pghero.rb +246 -0
- data/lib/pghero/connection.rb +5 -0
- data/lib/pghero/database.rb +175 -0
- data/lib/pghero/engine.rb +16 -0
- data/lib/pghero/methods/basic.rb +160 -0
- data/lib/pghero/methods/connections.rb +77 -0
- data/lib/pghero/methods/constraints.rb +30 -0
- data/lib/pghero/methods/explain.rb +29 -0
- data/lib/pghero/methods/indexes.rb +332 -0
- data/lib/pghero/methods/kill.rb +28 -0
- data/lib/pghero/methods/maintenance.rb +93 -0
- data/lib/pghero/methods/queries.rb +75 -0
- data/lib/pghero/methods/query_stats.rb +349 -0
- data/lib/pghero/methods/replication.rb +74 -0
- data/lib/pghero/methods/sequences.rb +124 -0
- data/lib/pghero/methods/settings.rb +37 -0
- data/lib/pghero/methods/space.rb +141 -0
- data/lib/pghero/methods/suggested_indexes.rb +329 -0
- data/lib/pghero/methods/system.rb +287 -0
- data/lib/pghero/methods/tables.rb +68 -0
- data/lib/pghero/methods/users.rb +87 -0
- data/lib/pghero/query_stats.rb +5 -0
- data/lib/pghero/space_stats.rb +5 -0
- data/lib/pghero/stats.rb +6 -0
- data/lib/pghero/version.rb +3 -0
- data/lib/tasks/pghero.rake +27 -0
- data/licenses/LICENSE-chart.js.txt +9 -0
- data/licenses/LICENSE-chartkick.js.txt +22 -0
- data/licenses/LICENSE-highlight.js.txt +29 -0
- data/licenses/LICENSE-jquery.txt +20 -0
- data/licenses/LICENSE-moment.txt +22 -0
- data/licenses/LICENSE-nouislider.txt +21 -0
- metadata +130 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module PgHero
|
2
|
+
module HomeHelper
|
3
|
+
def pghero_pretty_ident(table, schema: nil)
|
4
|
+
ident = table
|
5
|
+
if schema && schema != "public"
|
6
|
+
ident = "#{schema}.#{table}"
|
7
|
+
end
|
8
|
+
if ident =~ /\A[a-z0-9_]+\z/
|
9
|
+
ident
|
10
|
+
else
|
11
|
+
@database.quote_ident(ident)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def pghero_js_var(name, value)
|
16
|
+
"var #{name} = #{json_escape(value.to_json(root: false))};".html_safe
|
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
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= [@databases.size > 1 ? @database.name : "PgHero", @title].compact.join(" / ") %></title>
|
5
|
+
|
6
|
+
<meta charset="utf-8" />
|
7
|
+
<link rel="shortcut icon" type="image/x-icon" href="<%= root_path %>favicon.png">
|
8
|
+
<link rel="stylesheet" media="screen" href="<%= root_path %>application.css">
|
9
|
+
<script type="text/javascript" src="<%= root_path %>application.js"></script>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<div class="container">
|
13
|
+
<div class="alert alert-<%= alert ? "danger" : "info" %>">
|
14
|
+
<% if alert %>
|
15
|
+
<%= alert %>
|
16
|
+
<% elsif notice %>
|
17
|
+
<%= notice %>
|
18
|
+
<% elsif Rails.env.development? %>
|
19
|
+
Do not use development information to make decisions about your production environment
|
20
|
+
<% else %>
|
21
|
+
<%= link_to "PgHero", root_path %>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div class="grid">
|
26
|
+
<div class="col-3-12">
|
27
|
+
<% if @databases.size > 1 %>
|
28
|
+
<p class="nav-header"><%= @database.name %></p>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<ul class="nav">
|
32
|
+
<!-- poor man's active_link_to -->
|
33
|
+
<li class="<%= controller.action_name == "index" ? "active" : "" %>"><%= link_to "Overview", root_path %></li>
|
34
|
+
<% if @system_stats_enabled %>
|
35
|
+
<li class="<%= controller.action_name == "system" ? "active" : "" %>"><%= link_to "System", system_path %></li>
|
36
|
+
<% end %>
|
37
|
+
<% if @query_stats_enabled %>
|
38
|
+
<li class="<%= controller.action_name == "queries" ? "active" : "" %>"><%= link_to "Queries", queries_path %></li>
|
39
|
+
<% end %>
|
40
|
+
<li class="<%= controller.action_name == "space" ? "active" : "" %>"><%= link_to "Space", space_path %></li>
|
41
|
+
<li class="<%= controller.action_name == "connections" ? "active" : "" %>"><%= link_to "Connections", connections_path %></li>
|
42
|
+
<li class="<%= controller.action_name == "live_queries" ? "active" : "" %>"><%= link_to "Live Queries", live_queries_path %></li>
|
43
|
+
<% unless @database.replica? %>
|
44
|
+
<li class="<%= controller.action_name == "maintenance" ? "active" : "" %>"><%= link_to "Maintenance", maintenance_path %></li>
|
45
|
+
<% end %>
|
46
|
+
<li class="<%= controller.action_name == "explain" ? "active" : "" %>"><%= link_to "Explain", explain_path %></li>
|
47
|
+
<li class="<%= controller.action_name == "tune" ? "active" : "" %>"><%= link_to "Tune", tune_path %></li>
|
48
|
+
</ul>
|
49
|
+
|
50
|
+
<% if @databases.size > 1 %>
|
51
|
+
<p class="nav-header">Databases</p>
|
52
|
+
<ul class="nav">
|
53
|
+
<% @databases.each do |database| %>
|
54
|
+
<li class="<%= ("active-database" if @database.id == database.id) %>">
|
55
|
+
<%= link_to database.name, action_name == "show_query" ? root_path(database: database.id) : {database: database.id} %>
|
56
|
+
</li>
|
57
|
+
<% end %>
|
58
|
+
</ul>
|
59
|
+
<% end %>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div class="col-9-12">
|
63
|
+
<%= yield %>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
</body>
|
68
|
+
</html>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<table class="table">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th>Top Sources</th>
|
5
|
+
<th style="width: 20%;">Connections</th>
|
6
|
+
</tr>
|
7
|
+
</thead>
|
8
|
+
<tbody>
|
9
|
+
<% connection_sources.each do |source| %>
|
10
|
+
<tr>
|
11
|
+
<td><%= source[:source] %> <div class="text-muted"><%= [source[:user], source[:database], source[:ip]].compact.join(" - ") %></div></td>
|
12
|
+
<td><%= number_with_delimiter(source[:total_connections]) %></td>
|
13
|
+
</tr>
|
14
|
+
<% end %>
|
15
|
+
</tbody>
|
16
|
+
</table>
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<table class="table queries">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th style="width: 25%;">Pid</th>
|
5
|
+
<th style="width: 25%;">Duration</th>
|
6
|
+
<th style="width: 25%;">State</th>
|
7
|
+
<th style="width: 25%;"></th>
|
8
|
+
</tr>
|
9
|
+
</thead>
|
10
|
+
<tbody>
|
11
|
+
<% queries.reverse.each do |query| %>
|
12
|
+
<tr>
|
13
|
+
<td><%= query[:pid] %></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>
|
25
|
+
<td>
|
26
|
+
<%= query[:state] %>
|
27
|
+
<% if vacuum_progress[query[:pid]] %>
|
28
|
+
<br />
|
29
|
+
<strong><%= vacuum_progress[query[:pid]][:phase] %></strong>
|
30
|
+
<% end %>
|
31
|
+
</td>
|
32
|
+
<td class="text-right">
|
33
|
+
<% unless @database.filter_data %>
|
34
|
+
<%= button_to "Explain", explain_path, params: {query: query[:query]}, form: {target: "_blank"}, class: "btn btn-info" %>
|
35
|
+
<% end %>
|
36
|
+
<%= button_to "Kill", kill_path(pid: query[:pid]), class: "btn btn-danger" %>
|
37
|
+
</td>
|
38
|
+
</tr>
|
39
|
+
<tr>
|
40
|
+
<td colspan="6" style="border-top: none; padding: 0;">
|
41
|
+
<%= query[:source] %> <span class="text-muted"><%= query[:user] %></span>
|
42
|
+
<pre style="margin-top: 1em;"><code><%= query[:query] %></code></pre>
|
43
|
+
</td>
|
44
|
+
</tr>
|
45
|
+
<% end %>
|
46
|
+
</tbody>
|
47
|
+
</table>
|
48
|
+
|
49
|
+
<script>
|
50
|
+
highlightQueries();
|
51
|
+
</script>
|
@@ -0,0 +1,72 @@
|
|
1
|
+
<table class="table queries-table">
|
2
|
+
<% unless local_assigns[:xhr] %>
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Total Time", {sort: nil}, data: {sort: nil}) : "Total Time" %></th>
|
6
|
+
<th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Average Time", {sort: "average_time"}, data: {sort: "average_time"}) : "Average Time" %></th>
|
7
|
+
<th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Calls", {sort: "calls"}, data: {sort: "calls"}) : "Calls" %></th>
|
8
|
+
</tr>
|
9
|
+
</thead>
|
10
|
+
<% end %>
|
11
|
+
<tbody id="queries">
|
12
|
+
<% if queries.empty? %>
|
13
|
+
<tr>
|
14
|
+
<td colspan="3">
|
15
|
+
<p class="queries-info text-muted">
|
16
|
+
<% if local_assigns[:xhr] %>
|
17
|
+
No data available for this time.
|
18
|
+
<% else %>
|
19
|
+
...
|
20
|
+
<% end %>
|
21
|
+
</p>
|
22
|
+
</td>
|
23
|
+
</tr>
|
24
|
+
<% end %>
|
25
|
+
<% queries.each do |query| %>
|
26
|
+
<tr>
|
27
|
+
<td>
|
28
|
+
<%= number_with_delimiter(query[:total_minutes].round) %> min
|
29
|
+
<span class="percent">
|
30
|
+
<% percent = query[:total_percent] %>
|
31
|
+
<% if percent > 1 %>
|
32
|
+
<%= percent.round %>%
|
33
|
+
<% elsif percent > 0.1 %>
|
34
|
+
<%= percent.round(1) %>%
|
35
|
+
<% else %>
|
36
|
+
< 0.1%
|
37
|
+
<% end %>
|
38
|
+
</span>
|
39
|
+
</td>
|
40
|
+
<td>
|
41
|
+
<%= number_with_delimiter(query[:average_time].round) %> ms
|
42
|
+
</td>
|
43
|
+
<td>
|
44
|
+
<%= number_with_delimiter(query[:calls]) %>
|
45
|
+
|
46
|
+
<span class="user">
|
47
|
+
<% if query[:user] %>
|
48
|
+
<%= query[:user] %>
|
49
|
+
<% if @show_details %>
|
50
|
+
·
|
51
|
+
<% end %>
|
52
|
+
<% end %>
|
53
|
+
<% if @show_details && query[:query_hash] %>
|
54
|
+
<%= link_to "details", show_query_path(query[:query_hash], user: query[:user]), target: "_blank" %>
|
55
|
+
<% end %>
|
56
|
+
</span>
|
57
|
+
</td>
|
58
|
+
</tr>
|
59
|
+
<tr>
|
60
|
+
<td colspan="3" style="border-top: none; padding: 0;">
|
61
|
+
<pre><code style="max-height: 230px; overflow: hidden;" onclick="this.style.maxHeight = 'none';"><%= query[:query] %></code></pre>
|
62
|
+
<% if query[:query] == "<insufficient privilege>" %>
|
63
|
+
<p class="text-muted">For security reasons, only superusers can see queries executed by other users.</p>
|
64
|
+
<% end %>
|
65
|
+
<% if local_assigns[:suggested_indexes] != false && (details = @suggested_indexes_by_query[query[:query]]) && (details[:index] || @debug) %>
|
66
|
+
<%= render partial: "suggested_index", locals: {index: details[:index], details: details} %>
|
67
|
+
<% end %>
|
68
|
+
</td>
|
69
|
+
</tr>
|
70
|
+
<% end %>
|
71
|
+
</tbody>
|
72
|
+
</table>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<div id="slider-container">
|
2
|
+
<div id="slider"></div>
|
3
|
+
<div id="range-end"></div>
|
4
|
+
<div id="range-start"></div>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<script>
|
8
|
+
<%= pghero_js_var("sort", @sort) %>
|
9
|
+
<%= pghero_js_var("minAverageTime", @min_average_time) %>
|
10
|
+
<%= pghero_js_var("minCalls", @min_calls) %>
|
11
|
+
<%= pghero_js_var("debug", @debug) %>
|
12
|
+
<%= pghero_js_var("startAt", params[:start_at] ? @start_at.to_i * 1000 : nil) %>
|
13
|
+
<%= pghero_js_var("endAt", @end_at.to_i * 1000) %>
|
14
|
+
|
15
|
+
initSlider();
|
16
|
+
</script>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<% if index && !details[:covering_index] %>
|
2
|
+
<% unless @debug %>
|
3
|
+
<div style="float: right; color: #f0ad4e; margin-top: 0px; padding: 10px; cursor: pointer;" onclick="document.getElementById('details-<%= index.object_id %>').style.display = 'block'; this.style.display = 'none';">Details</div>
|
4
|
+
<% end %>
|
5
|
+
<code><pre style="color: #eee; background-color: #333;">CREATE INDEX CONCURRENTLY ON <%= index[:table] %><% if index[:using] %> USING <%= index[:using] %><% end %> (<%= index[:columns].join(", ") %>)</pre></code>
|
6
|
+
<% end %>
|
7
|
+
<div id="details-<%= index.object_id %>" style="<%= "display: none;" unless @debug %>">
|
8
|
+
<code><pre style="color: #f0ad4e; background-color: #333;"><% if details[:explanation] %><%= details[:explanation] %>
|
9
|
+
<% end %><% if details[:row_estimates] %>Rows: <%= details[:rows] %>
|
10
|
+
Row progression: <%= details[:row_progression].to_a.join(", ") %>
|
11
|
+
|
12
|
+
Row estimates
|
13
|
+
<%= details[:row_estimates].to_a.map { |k, v| "- #{k}: #{v}" }.join("\n") %><% end %><% if details[:table_indexes] %>
|
14
|
+
|
15
|
+
Existing indexes
|
16
|
+
<% details[:table_indexes].sort_by { |i| [i[:primary] ? 0 : 1, i[:columns]] }.each do |i3| %>- <%= i3[:columns].join(", ") %><% if i3[:using] != "btree" %> <%= i3[:using].to_s.upcase %><% end %><% if i3[:primary] %> PRIMARY<% elsif i3[:unique] %> UNIQUE<% end %>
|
17
|
+
<% end %><% end %></pre></code>
|
18
|
+
</div>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<h1>Connections</h1>
|
3
|
+
|
4
|
+
<p><%= pluralize(@total_connections, "connection") %></p>
|
5
|
+
|
6
|
+
<% if @total_connections > 0 %>
|
7
|
+
<h3>By Database</h3>
|
8
|
+
|
9
|
+
<div id="chart-1" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
10
|
+
<script>
|
11
|
+
new Chartkick.PieChart("chart-1", <%= json_escape(@connections_by_database.to_json).html_safe %>);
|
12
|
+
</script>
|
13
|
+
|
14
|
+
<h3>By User</h3>
|
15
|
+
|
16
|
+
<div id="chart-2" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
17
|
+
<script>
|
18
|
+
new Chartkick.PieChart("chart-2", <%= json_escape(@connections_by_user.to_json).html_safe %>);
|
19
|
+
</script>
|
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
|
+
|
30
|
+
<%= render partial: "connections_table", locals: {connection_sources: @connection_sources} %>
|
31
|
+
<% end %>
|
32
|
+
</div>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<h1>Explain</h1>
|
3
|
+
|
4
|
+
<%= form_tag explain_path do %>
|
5
|
+
<div class="field"><%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %></div>
|
6
|
+
<p>
|
7
|
+
<%= submit_tag "Explain", class: "btn btn-info", style: "margin-right: 10px;" %>
|
8
|
+
<%= submit_tag "Analyze", class: "btn btn-danger", style: "margin-right: 10px;" %>
|
9
|
+
<%= submit_tag "Visualize", class: "btn btn-danger" %>
|
10
|
+
</p>
|
11
|
+
<% end %>
|
12
|
+
|
13
|
+
<% if @explanation %>
|
14
|
+
<% if @visualize %>
|
15
|
+
<p>Paste the output below into the <%= link_to "Postgres Explain Visualizer", "https://tatiyants.com/pev/#/plans/new", target: "_blank" %></p>
|
16
|
+
<% end %>
|
17
|
+
<pre><code><%= @explanation %></code></pre>
|
18
|
+
<% unless @visualize %>
|
19
|
+
<p><%= link_to "See how to interpret this", "https://www.postgresql.org/docs/current/static/using-explain.html", target: "_blank" %></p>
|
20
|
+
<% end %>
|
21
|
+
<% if (index = @suggested_index) %>
|
22
|
+
<%= render partial: "suggested_index", locals: {index: index, details: index[:details]} %>
|
23
|
+
<% end %>
|
24
|
+
<% elsif @error %>
|
25
|
+
<div class="alert alert-danger"><%= @error %></div>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
@@ -0,0 +1,518 @@
|
|
1
|
+
<div id="status">
|
2
|
+
<% if @replica %>
|
3
|
+
<div class="alert alert-<%= @good_replication_lag ? "success" : "warning" %>">
|
4
|
+
<% if @replication_lag %>
|
5
|
+
<% if @good_replication_lag %>
|
6
|
+
Healthy replication lag
|
7
|
+
<% else %>
|
8
|
+
High replication lag
|
9
|
+
<% end %>
|
10
|
+
<span class="tiny"><%= number_with_delimiter((@replication_lag * 1000).round) %> ms</span>
|
11
|
+
<% else %>
|
12
|
+
Replication lag not supported
|
13
|
+
<% end %>
|
14
|
+
</div>
|
15
|
+
<% elsif @inactive_replication_slots.any? %>
|
16
|
+
<div class="alert alert-warning">
|
17
|
+
<%= pluralize(@inactive_replication_slots.size, "inactive replication slot") %>
|
18
|
+
</div>
|
19
|
+
<% end %>
|
20
|
+
<div class="alert alert-<%= @long_running_queries.empty? ? "success" : "warning" %>">
|
21
|
+
<% if @long_running_queries.any? %>
|
22
|
+
<%= pluralize(@long_running_queries.size, "long running query") %>
|
23
|
+
<% else %>
|
24
|
+
No long running queries
|
25
|
+
<% end %>
|
26
|
+
<% if @autovacuum_queries.any? %>
|
27
|
+
<span class="tiny"><%= @autovacuum_queries.size %> autovacuum</span>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
<% if @extended %>
|
31
|
+
<div class="alert alert-<%= @good_cache_rate ? "success" : "warning" %>">
|
32
|
+
<% if @good_cache_rate %>
|
33
|
+
Cache hit rate above <%= @database.cache_hit_rate_threshold %>%
|
34
|
+
<% else %>
|
35
|
+
Low cache hit rate
|
36
|
+
<% end %>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
39
|
+
<div class="alert alert-<%= @good_total_connections && @good_idle_connections ? "success" : "warning" %>">
|
40
|
+
<% if !@good_total_connections %>
|
41
|
+
High number of connections <span class="tiny"><%= @total_connections %></span>
|
42
|
+
<% elsif !@good_idle_connections %>
|
43
|
+
High number of connections idle in transaction <span class="tiny"><%= @idle_connections %></span>
|
44
|
+
<% else %>
|
45
|
+
Connections healthy <span class="tiny"><%= @total_connections %></span>
|
46
|
+
<% end %>
|
47
|
+
</div>
|
48
|
+
<div class="alert alert-<%= @transaction_id_danger.empty? ? "success" : "warning" %>">
|
49
|
+
<% if @transaction_id_danger.any? %>
|
50
|
+
<%= pluralize(@transaction_id_danger.size, "table") %> not vacuuming properly
|
51
|
+
<% else %>
|
52
|
+
Vacuuming healthy
|
53
|
+
<% end %>
|
54
|
+
</div>
|
55
|
+
<div class="alert alert-<%= @sequence_danger && @sequence_danger.empty? ? "success" : "warning" %>">
|
56
|
+
<% if @sequence_danger.any? %>
|
57
|
+
<%= pluralize(@sequence_danger.size, "column") %> approaching overflow
|
58
|
+
<% else %>
|
59
|
+
No columns near integer overflow
|
60
|
+
<% end %>
|
61
|
+
<% if @unreadable_sequences.any? %>
|
62
|
+
(<%= link_to pluralize(@unreadable_sequences.size, "unreadable sequence", "unreadable sequences"), {unreadable: "t"} %>)
|
63
|
+
<% end %>
|
64
|
+
</div>
|
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
|
+
<% else %>
|
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 %>
|
76
|
+
<% end %>
|
77
|
+
</div>
|
78
|
+
<% if @duplicate_indexes %>
|
79
|
+
<div class="alert alert-<%= @duplicate_indexes.empty? ? "success" : "warning" %>">
|
80
|
+
<% if @duplicate_indexes.any? %>
|
81
|
+
<%= pluralize(@duplicate_indexes.size, "duplicate index", "duplicate indexes") %>
|
82
|
+
<% else %>
|
83
|
+
No duplicate indexes
|
84
|
+
<% end %>
|
85
|
+
</div>
|
86
|
+
<% end %>
|
87
|
+
<% if @database.suggested_indexes_enabled? %>
|
88
|
+
<div class="alert alert-<%= @suggested_indexes.empty? ? "success" : "warning" %>">
|
89
|
+
<% if @suggested_indexes.any? %>
|
90
|
+
<%= pluralize(@suggested_indexes.size, "suggested index", "suggested indexes") %>
|
91
|
+
<% else %>
|
92
|
+
No suggested indexes
|
93
|
+
<% end %>
|
94
|
+
</div>
|
95
|
+
<% end %>
|
96
|
+
<div class="alert alert-<%= @query_stats_enabled && @slow_queries.empty? ? "success" : "warning" %>">
|
97
|
+
<% if !@query_stats_enabled %>
|
98
|
+
Query stats must be enabled for slow queries
|
99
|
+
<% elsif @slow_queries.any? %>
|
100
|
+
<%= pluralize(@slow_queries.size, "slow query") %>
|
101
|
+
<% else %>
|
102
|
+
No slow queries
|
103
|
+
<% end %>
|
104
|
+
</div>
|
105
|
+
<% if @extended %>
|
106
|
+
<div class="alert alert-<%= @unused_indexes.empty? ? "success" : "warning" %>">
|
107
|
+
<% if @unused_indexes.any? %>
|
108
|
+
<%= pluralize(@unused_indexes.size, "unused index", "unused indexes") %>
|
109
|
+
<% else %>
|
110
|
+
No unused indexes
|
111
|
+
<% end %>
|
112
|
+
</div>
|
113
|
+
<% end %>
|
114
|
+
</div>
|
115
|
+
|
116
|
+
<% if params[:unreadable] && @unreadable_sequences.any? %>
|
117
|
+
<div class="content">
|
118
|
+
<h1>Unreadable Sequences</h1>
|
119
|
+
|
120
|
+
<p>This is likely due to missing privileges. Make sure your user has the SELECT privilege for each sequence.</p>
|
121
|
+
|
122
|
+
<table class="table">
|
123
|
+
<thead>
|
124
|
+
<tr>
|
125
|
+
<th style="width: 33%;">Column</th>
|
126
|
+
<th>Sequence</th>
|
127
|
+
</tr>
|
128
|
+
</thead>
|
129
|
+
<tbody>
|
130
|
+
<% @unreadable_sequences.each do |sequence| %>
|
131
|
+
<tr>
|
132
|
+
<td>
|
133
|
+
<%= sequence[:table] %>.<%= sequence[:column] %>
|
134
|
+
<% if sequence[:table_schema] != "public" %>
|
135
|
+
<span class="text-muted"><%= sequence[:table_schema] %></span>
|
136
|
+
<% end %>
|
137
|
+
</td>
|
138
|
+
<td>
|
139
|
+
<% if sequence[:sequence] %>
|
140
|
+
<%= sequence[:sequence] %>
|
141
|
+
<% if sequence[:schema] && sequence[:schema] != "public" %>
|
142
|
+
<span class="text-muted"><%= sequence[:schema] %></span>
|
143
|
+
<% end %>
|
144
|
+
<% else %>
|
145
|
+
Unable to parse: <%= sequence[:default_value] %>
|
146
|
+
<% end %>
|
147
|
+
</td>
|
148
|
+
</tr>
|
149
|
+
<% end %>
|
150
|
+
</tbody>
|
151
|
+
</table>
|
152
|
+
</div>
|
153
|
+
<% end %>
|
154
|
+
|
155
|
+
<% if @replica && !@good_replication_lag %>
|
156
|
+
<div class="content">
|
157
|
+
<h1>High Replication Lag</h1>
|
158
|
+
|
159
|
+
<p><%= pluralize(@replication_lag.round, "second") %></p>
|
160
|
+
</div>
|
161
|
+
<% end %>
|
162
|
+
|
163
|
+
<% if @inactive_replication_slots && @inactive_replication_slots.any? %>
|
164
|
+
<div class="content">
|
165
|
+
<h1>Inactive Replication Slots</h1>
|
166
|
+
<p>Inactive replication slots can cause a lot of disk space to be consumed.</p>
|
167
|
+
<p>For each, run:</p>
|
168
|
+
<pre><code>SELECT pg_drop_replication_slot('slot_name');</code></pre>
|
169
|
+
<table class="table">
|
170
|
+
<thead>
|
171
|
+
<tr>
|
172
|
+
<th>Name</th>
|
173
|
+
</tr>
|
174
|
+
</thead>
|
175
|
+
<tbody>
|
176
|
+
<% @inactive_replication_slots.each do |slot| %>
|
177
|
+
<tr>
|
178
|
+
<td><%= slot[:slot_name] %></td>
|
179
|
+
</tr>
|
180
|
+
<% end %>
|
181
|
+
</tbody>
|
182
|
+
</table>
|
183
|
+
</div>
|
184
|
+
<% end %>
|
185
|
+
|
186
|
+
<% if @long_running_queries.any? %>
|
187
|
+
<div class="content">
|
188
|
+
<%= button_to "Kill All", kill_long_running_queries_path, class: "btn btn-danger", style: "float: right;" %>
|
189
|
+
<h1>Long Running Queries</h1>
|
190
|
+
|
191
|
+
<p>We recommend setting a statement timeout on all non-superusers with:</p>
|
192
|
+
|
193
|
+
<pre><code>ALTER ROLE <user> SET statement_timeout TO '60s';</code></pre>
|
194
|
+
|
195
|
+
<%= render partial: "live_queries_table", locals: {queries: @long_running_queries, vacuum_progress: {}} %>
|
196
|
+
</div>
|
197
|
+
<% end %>
|
198
|
+
|
199
|
+
<% if @extended && !@good_cache_rate %>
|
200
|
+
<div class="content">
|
201
|
+
<h1>Low Cache Hit Rate</h1>
|
202
|
+
|
203
|
+
<p>
|
204
|
+
Index Hit Rate: <%= (@index_hit_rate * 100).round(1) %>%
|
205
|
+
<br />
|
206
|
+
Table Hit Rate: <%= (@table_hit_rate * 100).round(1) %>%
|
207
|
+
</p>
|
208
|
+
|
209
|
+
<p>
|
210
|
+
The cache hit rate <%= link_to "should be above 99%", "https://devcenter.heroku.com/articles/understanding-postgres-data-caching", target: "_blank" %> in most cases. You can often increase this by adding more memory.
|
211
|
+
<!-- TODO better suggestions -->
|
212
|
+
</p>
|
213
|
+
</div>
|
214
|
+
<% end %>
|
215
|
+
|
216
|
+
<% if !@good_total_connections %>
|
217
|
+
<div class="content">
|
218
|
+
<h1>High Number of Connections</h1>
|
219
|
+
<p><%= pluralize(@total_connections, "connection") %></p>
|
220
|
+
|
221
|
+
<p><%= link_to "Use connection pooling", "http://www.craigkerstiens.com/2014/05/22/on-connection-pooling/", target: "_blank" %> for better performance. <%= link_to "PgBouncer", "https://wiki.postgresql.org/wiki/PgBouncer", target: "_blank" %> is a solid option.</p>
|
222
|
+
|
223
|
+
<%= render partial: "connections_table", locals: {connection_sources: @database.connection_sources.first(10)} %>
|
224
|
+
</div>
|
225
|
+
<% end %>
|
226
|
+
|
227
|
+
<% if !@good_idle_connections %>
|
228
|
+
<div class="content">
|
229
|
+
<h1>High Number of Connections Idle in Transaction</h1>
|
230
|
+
<p><%= pluralize(@idle_connections, "connection") %> idle in transaction</p>
|
231
|
+
<p>Avoid opening transactions and doing work outside the database.</p>
|
232
|
+
<p><%= link_to "View queries", live_queries_path(state: "idle in transaction") %></p>
|
233
|
+
</div>
|
234
|
+
<% end %>
|
235
|
+
|
236
|
+
<% if @transaction_id_danger.any? %>
|
237
|
+
<div class="content">
|
238
|
+
<h2>Vacuuming Needed</h2>
|
239
|
+
<p>The database <strong>will shutdown</strong> when there are fewer than 1,000,000 transactions left. <%= link_to "Read more", "https://www.postgresql.org/docs/current/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND", target: "_blank" %>.</p>
|
240
|
+
|
241
|
+
<p>Try <%= link_to "tuning autovacuum", "https://blog.2ndquadrant.com/autovacuum-tuning-basics/", target: "_blank" %> - specifically autovacuum_vacuum_cost_limit.</p>
|
242
|
+
|
243
|
+
<p>If that doesn’t work, for each table, run:</p>
|
244
|
+
<pre><code>VACUUM FREEZE VERBOSE table;</code></pre>
|
245
|
+
<table class="table">
|
246
|
+
<thead>
|
247
|
+
<tr>
|
248
|
+
<th>Table</th>
|
249
|
+
<th style="width: 20%;">Transactions Left</th>
|
250
|
+
</tr>
|
251
|
+
</thead>
|
252
|
+
<tbody>
|
253
|
+
<% @transaction_id_danger.each do |query| %>
|
254
|
+
<tr>
|
255
|
+
<td>
|
256
|
+
<%= query[:table] %>
|
257
|
+
<% if query[:schema] != "public" %>
|
258
|
+
<span class="text-muted"><%= query[:schema] %></span>
|
259
|
+
<% end %>
|
260
|
+
</td>
|
261
|
+
<td><%= number_with_delimiter(query[:transactions_left]) %></td>
|
262
|
+
</tr>
|
263
|
+
<% end %>
|
264
|
+
</tbody>
|
265
|
+
</table>
|
266
|
+
</div>
|
267
|
+
<% end %>
|
268
|
+
|
269
|
+
<% if @sequence_danger && @sequence_danger.any? %>
|
270
|
+
<div class="content">
|
271
|
+
<h2>Columns Near Overflow</h2>
|
272
|
+
<p>Consider changing columns to bigint to support a larger range of values.</p>
|
273
|
+
<table class="table">
|
274
|
+
<thead>
|
275
|
+
<tr>
|
276
|
+
<th>Column</th>
|
277
|
+
<th style="width: 20%;">Type</th>
|
278
|
+
<th style="width: 20%;">Values Left</th>
|
279
|
+
<th style="width: 10%;">% Left</th>
|
280
|
+
</tr>
|
281
|
+
</thead>
|
282
|
+
<tbody>
|
283
|
+
<% @sequence_danger.sort_by { |s| [s[:table], s[:column]] }.each do |query| %>
|
284
|
+
<tr>
|
285
|
+
<td>
|
286
|
+
<%= query[:table] %>.<%= query[:column] %>
|
287
|
+
<% if query[:table_schema] != "public" %>
|
288
|
+
<span class="text-muted"><%= query[:table_schema] %></span>
|
289
|
+
<% end %>
|
290
|
+
</td>
|
291
|
+
<td>
|
292
|
+
<%= query[:column_type] %>
|
293
|
+
</td>
|
294
|
+
<td>
|
295
|
+
<%= number_with_delimiter(query[:max_value] - query[:last_value]) %>
|
296
|
+
</td>
|
297
|
+
<td>
|
298
|
+
<%= number_to_percentage((query[:max_value] - query[:last_value]) * 100.0 / query[:max_value], precision: 2, significant: true) %>
|
299
|
+
</td>
|
300
|
+
</tr>
|
301
|
+
<% end %>
|
302
|
+
</tbody>
|
303
|
+
</table>
|
304
|
+
</div>
|
305
|
+
<% end %>
|
306
|
+
|
307
|
+
<% if @invalid_indexes.any? %>
|
308
|
+
<div class="content">
|
309
|
+
<h1>Invalid Indexes</h1>
|
310
|
+
|
311
|
+
<p>These indexes exist, but can’t be used. You should recreate them.</p>
|
312
|
+
|
313
|
+
<table class="table">
|
314
|
+
<thead>
|
315
|
+
<tr>
|
316
|
+
<th>Name</th>
|
317
|
+
</tr>
|
318
|
+
</thead>
|
319
|
+
<tbody>
|
320
|
+
<% @invalid_indexes.each do |index| %>
|
321
|
+
<tr>
|
322
|
+
<td>
|
323
|
+
<%= index[:name] %>
|
324
|
+
<% if index[:schema] != "public" %>
|
325
|
+
<span class="text-muted"><%= index[:schema] %></span>
|
326
|
+
<% end %>
|
327
|
+
</td>
|
328
|
+
</tr>
|
329
|
+
<tr>
|
330
|
+
<td style="border-top: none; padding: 0;">
|
331
|
+
<pre><code>DROP INDEX CONCURRENTLY <%= pghero_pretty_ident(index[:name], schema: index[:schema]) %>;
|
332
|
+
<%= index[:definition].sub("CREATE INDEX ", "CREATE INDEX CONCURRENTLY ") %>;</code></pre>
|
333
|
+
</td>
|
334
|
+
</tr>
|
335
|
+
<% end %>
|
336
|
+
</tbody>
|
337
|
+
</table>
|
338
|
+
</div>
|
339
|
+
<% end %>
|
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
|
+
|
374
|
+
<% if @duplicate_indexes && @duplicate_indexes.any? %>
|
375
|
+
<div class="content">
|
376
|
+
<h1>Duplicate Indexes</h1>
|
377
|
+
|
378
|
+
<p>
|
379
|
+
These indexes exist, but aren’t needed. Remove them
|
380
|
+
<% if @show_migrations %>
|
381
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration2').style.display = 'block';">with a migration</a>
|
382
|
+
<% end %>
|
383
|
+
for faster writes.
|
384
|
+
</p>
|
385
|
+
|
386
|
+
<div id="migration2" style="display: none;">
|
387
|
+
<pre>rails generate migration remove_unneeded_indexes</pre>
|
388
|
+
<p>And paste</p>
|
389
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
|
390
|
+
<%= pghero_remove_index(query[:unneeded_index]) %><% end %></pre>
|
391
|
+
</div>
|
392
|
+
|
393
|
+
<table class="table duplicate-indexes">
|
394
|
+
<thead>
|
395
|
+
<tr>
|
396
|
+
<th>Details</th>
|
397
|
+
</tr>
|
398
|
+
</thead>
|
399
|
+
<tbody>
|
400
|
+
<% @duplicate_indexes.each do |index| %>
|
401
|
+
<% unneeded_index = index[:unneeded_index] %>
|
402
|
+
<% covering_index = index[:covering_index] %>
|
403
|
+
<tr>
|
404
|
+
<td>
|
405
|
+
On <%= unneeded_index[:table] %>
|
406
|
+
<pre><%= unneeded_index[:name] %> (<%= unneeded_index[:columns].join(", ") %>)</pre>
|
407
|
+
is covered by
|
408
|
+
<pre><%= covering_index[:name] %> (<%= covering_index[:columns].join(", ") %>)</pre>
|
409
|
+
</td>
|
410
|
+
</tr>
|
411
|
+
<% end %>
|
412
|
+
</tbody>
|
413
|
+
</table>
|
414
|
+
</div>
|
415
|
+
<% end %>
|
416
|
+
|
417
|
+
<% if @suggested_indexes.any? %>
|
418
|
+
<div class="content">
|
419
|
+
<h1>Suggested Indexes</h1>
|
420
|
+
<p>
|
421
|
+
Add indexes to speed up queries.
|
422
|
+
<% if @show_migrations %>
|
423
|
+
Here’s a
|
424
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration3').style.display = 'block';">migration</a> to help.
|
425
|
+
<% end %>
|
426
|
+
</p>
|
427
|
+
|
428
|
+
<div id="migration3" style="display: none;">
|
429
|
+
<pre>rails generate migration add_suggested_indexes</pre>
|
430
|
+
<p>And paste</p>
|
431
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;">commit_db_transaction
|
432
|
+
<% @suggested_indexes.each do |index| %>
|
433
|
+
<% if index[:using] && index[:using] != "btree" %>
|
434
|
+
add_index <%= index[:table].to_sym.inspect %>, <%= index[:columns].first.inspect %>, using: <%= index[:using].inspect %>, algorithm: :concurrently
|
435
|
+
<% else %>
|
436
|
+
add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(", ") %>], algorithm: :concurrently<% end %>
|
437
|
+
<% end %></pre>
|
438
|
+
</div>
|
439
|
+
|
440
|
+
<% @suggested_indexes.each_with_index do |index, i| %>
|
441
|
+
<hr />
|
442
|
+
<%= render partial: "suggested_index", locals: {index: index, details: index[:details]} %>
|
443
|
+
<p>to speed up</p>
|
444
|
+
<%= render partial: "queries_table", locals: {queries: index[:queries].map { |q| @query_stats_by_query[q] }, suggested_indexes: false} %>
|
445
|
+
<% end %>
|
446
|
+
</div>
|
447
|
+
<% end %>
|
448
|
+
|
449
|
+
<% if !@query_stats_enabled %>
|
450
|
+
<div class="content">
|
451
|
+
<h1>Query Stats</h1>
|
452
|
+
|
453
|
+
<% if @query_stats_available && !@query_stats_extension_enabled %>
|
454
|
+
<p>
|
455
|
+
Query stats are available but not enabled.
|
456
|
+
<%= button_to "Enable", enable_query_stats_path, class: "btn btn-info" %>
|
457
|
+
</p>
|
458
|
+
<% else %>
|
459
|
+
<p>Make them available by adding the following lines to <code>postgresql.conf</code>:</p>
|
460
|
+
<pre>shared_preload_libraries = 'pg_stat_statements'
|
461
|
+
pg_stat_statements.track = all</pre>
|
462
|
+
<p>Restart the server for the changes to take effect.</p>
|
463
|
+
<% end %>
|
464
|
+
</div>
|
465
|
+
<% end %>
|
466
|
+
|
467
|
+
<% if @query_stats_enabled && @slow_queries.any? %>
|
468
|
+
<div class="content">
|
469
|
+
<h1>Slow Queries</h1>
|
470
|
+
|
471
|
+
<p>Slow queries take <%= @database.slow_query_ms %> ms or more on average and have been called at least <%= @database.slow_query_calls %> times.</p>
|
472
|
+
<p><%= link_to "Explain queries", explain_path %> to see where to add indexes.</p>
|
473
|
+
|
474
|
+
<%= render partial: "queries_table", locals: {queries: @slow_queries} %>
|
475
|
+
</div>
|
476
|
+
<% end %>
|
477
|
+
|
478
|
+
<% if @extended && @unused_indexes.any? %>
|
479
|
+
<div class="content">
|
480
|
+
<h1>Unused Indexes</h1>
|
481
|
+
|
482
|
+
<p>
|
483
|
+
Unused indexes cause unnecessary overhead. Remove them
|
484
|
+
<% if @show_migrations %>
|
485
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration').style.display = 'block';">with a migration</a>
|
486
|
+
<% end %>
|
487
|
+
for faster writes.
|
488
|
+
</p>
|
489
|
+
|
490
|
+
<div id="migration" style="display: none;">
|
491
|
+
<pre>rails generate migration remove_unused_indexes</pre>
|
492
|
+
<p>And paste</p>
|
493
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.each do |query| %>
|
494
|
+
<%= pghero_remove_index(query)%><% end %></pre>
|
495
|
+
</div>
|
496
|
+
|
497
|
+
<table class="table">
|
498
|
+
<thead>
|
499
|
+
<tr>
|
500
|
+
<th>Name</th>
|
501
|
+
<th style="width: 20%;">Index Size</th>
|
502
|
+
</tr>
|
503
|
+
</thead>
|
504
|
+
<tbody>
|
505
|
+
<% @unused_indexes.each do |query| %>
|
506
|
+
<tr>
|
507
|
+
<td><%= query[:index] %><div class="text-muted">on <%= query[:table] %></div></td>
|
508
|
+
<td><%= query[:size] %></td>
|
509
|
+
</tr>
|
510
|
+
<% end %>
|
511
|
+
</tbody>
|
512
|
+
</table>
|
513
|
+
</div>
|
514
|
+
<% end %>
|
515
|
+
|
516
|
+
<script>
|
517
|
+
highlightQueries();
|
518
|
+
</script>
|