pghero 1.7.0 → 2.0.0
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/.travis.yml +2 -0
- data/CHANGELOG.md +31 -0
- data/README.md +2 -2
- data/app/assets/javascripts/pghero/Chart.bundle.js +7512 -5661
- data/app/assets/javascripts/pghero/application.js +9 -0
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
- data/app/assets/stylesheets/pghero/application.css +54 -2
- data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
- data/app/controllers/pg_hero/home_controller.rb +148 -52
- data/app/helpers/pg_hero/base_helper.rb +15 -0
- data/app/views/layouts/pg_hero/application.html.erb +1 -1
- data/app/views/pg_hero/home/_connections_table.html.erb +2 -2
- data/app/views/pg_hero/home/_live_queries_table.html.erb +11 -7
- data/app/views/pg_hero/home/_queries_table.html.erb +21 -10
- data/app/views/pg_hero/home/_suggested_index.html.erb +1 -1
- data/app/views/pg_hero/home/connections.html.erb +2 -14
- data/app/views/pg_hero/home/explain.html.erb +1 -1
- data/app/views/pg_hero/home/index.html.erb +58 -22
- data/app/views/pg_hero/home/index_bloat.html.erb +69 -0
- data/app/views/pg_hero/home/maintenance.html.erb +7 -7
- data/app/views/pg_hero/home/queries.html.erb +10 -0
- data/app/views/pg_hero/home/relation_space.html.erb +9 -0
- data/app/views/pg_hero/home/show_query.html.erb +107 -0
- data/app/views/pg_hero/home/space.html.erb +64 -10
- data/config/routes.rb +4 -2
- data/guides/Rails.md +28 -1
- data/guides/Suggested-Indexes.md +1 -1
- data/lib/pghero.rb +25 -36
- data/lib/pghero/database.rb +5 -1
- data/lib/pghero/methods/basic.rb +78 -13
- data/lib/pghero/methods/connections.rb +16 -56
- data/lib/pghero/methods/explain.rb +2 -6
- data/lib/pghero/methods/indexes.rb +173 -18
- data/lib/pghero/methods/kill.rb +2 -2
- data/lib/pghero/methods/maintenance.rb +23 -26
- data/lib/pghero/methods/queries.rb +1 -23
- data/lib/pghero/methods/query_stats.rb +95 -96
- data/lib/pghero/methods/{replica.rb → replication.rb} +17 -4
- data/lib/pghero/methods/sequences.rb +4 -5
- data/lib/pghero/methods/space.rb +101 -8
- data/lib/pghero/methods/suggested_indexes.rb +49 -108
- data/lib/pghero/methods/system.rb +14 -10
- data/lib/pghero/methods/tables.rb +8 -8
- data/lib/pghero/methods/users.rb +10 -12
- data/lib/pghero/version.rb +1 -1
- data/lib/tasks/pghero.rake +1 -1
- data/test/basic_test.rb +38 -0
- data/test/best_index_test.rb +3 -3
- data/test/suggested_indexes_test.rb +0 -2
- data/test/test_helper.rb +38 -40
- metadata +11 -6
- data/app/views/pg_hero/home/index_usage.html.erb +0 -27
- data/test/explain_test.rb +0 -18
@@ -0,0 +1,15 @@
|
|
1
|
+
module PgHero
|
2
|
+
module BaseHelper
|
3
|
+
def pghero_pretty_ident(table, schema: nil)
|
4
|
+
ident = table
|
5
|
+
if schema && schema != "public"
|
6
|
+
indent = "#{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
|
+
end
|
15
|
+
end
|
@@ -51,7 +51,7 @@
|
|
51
51
|
<ul class="nav">
|
52
52
|
<% @databases.each do |database| %>
|
53
53
|
<li class="<%= ("active-database" if @database.id == database.id) %>">
|
54
|
-
<%= link_to database.name, database: database.id %>
|
54
|
+
<%= link_to database.name, action_name == "show_query" ? root_path(database: database.id) : {database: database.id} %>
|
55
55
|
</li>
|
56
56
|
<% end %>
|
57
57
|
</ul>
|
@@ -8,8 +8,8 @@
|
|
8
8
|
<tbody>
|
9
9
|
<% connection_sources.each do |source| %>
|
10
10
|
<tr>
|
11
|
-
<td><%= source[
|
12
|
-
<td><%= number_with_delimiter(source[
|
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
13
|
</tr>
|
14
14
|
<% end %>
|
15
15
|
</tbody>
|
@@ -10,21 +10,25 @@
|
|
10
10
|
<tbody>
|
11
11
|
<% queries.reverse.each do |query| %>
|
12
12
|
<tr>
|
13
|
-
<td><%= query[
|
14
|
-
<td><%= number_with_delimiter(query[
|
15
|
-
<td><%= query[
|
13
|
+
<td><%= query[:pid] %></td>
|
14
|
+
<td><%= number_with_delimiter(query[:duration_ms].round) %> ms</td>
|
15
|
+
<td><%= query[:state] %></td>
|
16
16
|
<td class="text-right">
|
17
|
-
<% button_path, button_options = Rails.version >= "4.1" ? [explain_path, {params: {query: query[
|
17
|
+
<% button_path, button_options = Rails.version >= "4.1" ? [explain_path, {params: {query: query[:query]}}] : [explain_path(query: query[:query]), {}] %>
|
18
18
|
<%= button_to "Explain", button_path, button_options.merge(form: {target: "_blank"}, class: "btn btn-info") %>
|
19
|
-
<%= button_to "Kill", kill_path(pid: query[
|
19
|
+
<%= button_to "Kill", kill_path(pid: query[:pid]), class: "btn btn-danger" %>
|
20
20
|
</td>
|
21
21
|
</tr>
|
22
22
|
<tr>
|
23
23
|
<td colspan="6" style="border-top: none; padding: 0;">
|
24
|
-
<%= query[
|
25
|
-
<pre><%= query[
|
24
|
+
<%= query[:source] %> <span class="text-muted"><%= query[:user] %></span>
|
25
|
+
<pre style="margin-top: 1em;"><code><%= query[:query] %></code></pre>
|
26
26
|
</td>
|
27
27
|
</tr>
|
28
28
|
<% end %>
|
29
29
|
</tbody>
|
30
30
|
</table>
|
31
|
+
|
32
|
+
<script>
|
33
|
+
highlightQueries();
|
34
|
+
</script>
|
@@ -25,9 +25,9 @@
|
|
25
25
|
<% queries.each do |query| %>
|
26
26
|
<tr>
|
27
27
|
<td>
|
28
|
-
<%= number_with_delimiter(query[
|
28
|
+
<%= number_with_delimiter(query[:total_minutes].round) %> min
|
29
29
|
<span class="percent">
|
30
|
-
<% percent = query[
|
30
|
+
<% percent = query[:total_percent] %>
|
31
31
|
<% if percent > 1 %>
|
32
32
|
<%= percent.round %>%
|
33
33
|
<% elsif percent > 0.1 %>
|
@@ -37,21 +37,32 @@
|
|
37
37
|
<% end %>
|
38
38
|
</span>
|
39
39
|
</td>
|
40
|
-
<td><%= number_with_delimiter(query["average_time"].to_f.round) %> ms</td>
|
41
40
|
<td>
|
42
|
-
<%= number_with_delimiter(query[
|
43
|
-
|
44
|
-
|
45
|
-
|
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 %>
|
54
|
+
<%= link_to "details", show_query_path(query[:query_hash]), target: "_blank" %>
|
55
|
+
<% end %>
|
56
|
+
</span>
|
46
57
|
</td>
|
47
58
|
</tr>
|
48
59
|
<tr>
|
49
60
|
<td colspan="3" style="border-top: none; padding: 0;">
|
50
|
-
<code
|
51
|
-
<% if query[
|
61
|
+
<pre><code style="max-height: 230px; overflow: hidden;" onclick="this.style.maxHeight = 'none';"><%= query[:query] %></code></pre>
|
62
|
+
<% if query[:query] == "<insufficient privilege>" %>
|
52
63
|
<p class="text-muted">For security reasons, only superusers can see queries executed by other users.</p>
|
53
64
|
<% end %>
|
54
|
-
<% if local_assigns[:suggested_indexes] != false && (details = @suggested_indexes_by_query[query[
|
65
|
+
<% if local_assigns[:suggested_indexes] != false && (details = @suggested_indexes_by_query[query[:query]]) && (details[:index] || @debug) %>
|
55
66
|
<%= render partial: "suggested_index", locals: {index: details[:index], details: details} %>
|
56
67
|
<% end %>
|
57
68
|
</td>
|
@@ -13,6 +13,6 @@ Row estimates
|
|
13
13
|
<%= details[:row_estimates].to_a.map { |k, v| "- #{k}: #{v}" }.join("\n") %><% end %><% if details[:table_indexes] %>
|
14
14
|
|
15
15
|
Existing indexes
|
16
|
-
<% details[:table_indexes].sort_by { |i| [
|
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
17
|
<% end %><% end %></pre></code>
|
18
18
|
</div>
|
@@ -6,28 +6,16 @@
|
|
6
6
|
<% if @total_connections > 0 %>
|
7
7
|
<h3>By Database</h3>
|
8
8
|
|
9
|
-
<% top_connections = Hash.new(0) %>
|
10
|
-
<% @connection_sources.each do |source| %>
|
11
|
-
<% top_connections[source["database"]] += source["total_connections"].to_i %>
|
12
|
-
<% end %>
|
13
|
-
<% top_connections = top_connections.sort_by { |k, v| [-v, k] } %>
|
14
|
-
|
15
9
|
<div id="chart-1" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
16
10
|
<script>
|
17
|
-
new Chartkick.PieChart("chart-1", <%= json_escape(
|
11
|
+
new Chartkick.PieChart("chart-1", <%= json_escape(@connections_by_database.to_json).html_safe %>);
|
18
12
|
</script>
|
19
13
|
|
20
14
|
<h3>By User</h3>
|
21
15
|
|
22
|
-
<% top_connections = Hash.new(0) %>
|
23
|
-
<% @connection_sources.each do |source| %>
|
24
|
-
<% top_connections[source["user"]] += source["total_connections"].to_i %>
|
25
|
-
<% end %>
|
26
|
-
<% top_connections = top_connections.sort_by { |k, v| [-v, k] } %>
|
27
|
-
|
28
16
|
<div id="chart-2" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
29
17
|
<script>
|
30
|
-
new Chartkick.PieChart("chart-2", <%= json_escape(
|
18
|
+
new Chartkick.PieChart("chart-2", <%= json_escape(@connections_by_user.to_json).html_safe %>);
|
31
19
|
</script>
|
32
20
|
|
33
21
|
<%= render partial: "connections_table", locals: {connection_sources: @connection_sources} %>
|
@@ -14,7 +14,7 @@
|
|
14
14
|
<% if @visualize %>
|
15
15
|
<p>Paste the output below into the <%= link_to "Postgres Explain Visualizer", "http://tatiyants.com/pev/#/plans/new", target: "_blank" %></p>
|
16
16
|
<% end %>
|
17
|
-
<pre><%= @explanation %></pre>
|
17
|
+
<pre><code><%= @explanation %></code></pre>
|
18
18
|
<% unless @visualize %>
|
19
19
|
<p><%= link_to "See how to interpret this", "http://www.postgresql.org/docs/current/static/using-explain.html", target: "_blank" %></p>
|
20
20
|
<% end %>
|
@@ -8,6 +8,10 @@
|
|
8
8
|
<% end %>
|
9
9
|
<span class="tiny"><%= number_with_delimiter((@replication_lag * 1000).round) %> ms</span>
|
10
10
|
</div>
|
11
|
+
<% elsif @inactive_replication_slots.any? %>
|
12
|
+
<div class="alert alert-warning">
|
13
|
+
<%= pluralize(@inactive_replication_slots.size, "inactive replication slot") %>
|
14
|
+
</div>
|
11
15
|
<% end %>
|
12
16
|
<div class="alert alert-<%= @long_running_queries.empty? ? "success" : "warning" %>">
|
13
17
|
<% if @long_running_queries.any? %>
|
@@ -103,6 +107,29 @@
|
|
103
107
|
</div>
|
104
108
|
<% end %>
|
105
109
|
|
110
|
+
<% if @inactive_replication_slots && @inactive_replication_slots.any? %>
|
111
|
+
<div class="content">
|
112
|
+
<h1>Inactive Replication Slots</h1>
|
113
|
+
<p>Inactive replication slots can cause a lot of disk space to be consumed.</p>
|
114
|
+
<p>For each, run:</p>
|
115
|
+
<pre><code>SELECT pg_drop_replication_slot('slot_name');</code></pre>
|
116
|
+
<table class="table">
|
117
|
+
<thead>
|
118
|
+
<tr>
|
119
|
+
<th>Name</th>
|
120
|
+
</tr>
|
121
|
+
</thead>
|
122
|
+
<tbody>
|
123
|
+
<% @inactive_replication_slots.each do |slot| %>
|
124
|
+
<tr>
|
125
|
+
<td><%= slot[:slot_name] %></td>
|
126
|
+
</tr>
|
127
|
+
<% end %>
|
128
|
+
</tbody>
|
129
|
+
</table>
|
130
|
+
</div>
|
131
|
+
<% end %>
|
132
|
+
|
106
133
|
<% if @long_running_queries.any? %>
|
107
134
|
<div class="content">
|
108
135
|
<%= button_to "Kill All", kill_long_running_queries_path, class: "btn btn-danger", style: "float: right;" %>
|
@@ -136,7 +163,7 @@
|
|
136
163
|
|
137
164
|
<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>
|
138
165
|
|
139
|
-
<%= render partial: "connections_table", locals: {connection_sources: @database.connection_sources
|
166
|
+
<%= render partial: "connections_table", locals: {connection_sources: @database.connection_sources.first(10)} %>
|
140
167
|
</div>
|
141
168
|
<% end %>
|
142
169
|
|
@@ -145,7 +172,7 @@
|
|
145
172
|
<h2>Vacuuming Needed</h2>
|
146
173
|
<p>The database <strong>will shutdown</strong> when there are fewer than 1,000,000 transactions left. <%= link_to "Read more", "http://www.postgresql.org/docs/9.1/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND", target: "_blank" %>.</p>
|
147
174
|
<p>For each table, run:</p>
|
148
|
-
<code
|
175
|
+
<pre><code>VACUUM FREEZE VERBOSE table;</code></pre>
|
149
176
|
<table class="table">
|
150
177
|
<thead>
|
151
178
|
<tr>
|
@@ -156,8 +183,13 @@
|
|
156
183
|
<tbody>
|
157
184
|
<% @transaction_id_danger.each do |query| %>
|
158
185
|
<tr>
|
159
|
-
<td
|
160
|
-
|
186
|
+
<td>
|
187
|
+
<%= query[:table] %>
|
188
|
+
<% if query[:schema] != "public" %>
|
189
|
+
<span class="text-muted"><%= query[:schema] %></span>
|
190
|
+
<% end %>
|
191
|
+
</td>
|
192
|
+
<td><%= number_with_delimiter(query[:transactions_left]) %></td>
|
161
193
|
</tr>
|
162
194
|
<% end %>
|
163
195
|
</tbody>
|
@@ -179,19 +211,19 @@
|
|
179
211
|
</tr>
|
180
212
|
</thead>
|
181
213
|
<tbody>
|
182
|
-
<% @sequence_danger.sort_by { |s| [s[
|
214
|
+
<% @sequence_danger.sort_by { |s| [s[:table], s[:column]] }.each do |query| %>
|
183
215
|
<tr>
|
184
216
|
<td>
|
185
|
-
<%= query[
|
217
|
+
<%= query[:table] %>.<%= query[:column] %>
|
186
218
|
</td>
|
187
219
|
<td>
|
188
|
-
<%= query[
|
220
|
+
<%= query[:column_type] %>
|
189
221
|
</td>
|
190
222
|
<td>
|
191
|
-
<%= number_with_delimiter(query[
|
223
|
+
<%= number_with_delimiter(query[:max_value] - query[:last_value]) %>
|
192
224
|
</td>
|
193
225
|
<td>
|
194
|
-
<%= number_to_percentage((query[
|
226
|
+
<%= number_to_percentage((query[:max_value] - query[:last_value]) * 100.0 / query[:max_value], precision: 2, significant: true) %>
|
195
227
|
</td>
|
196
228
|
</tr>
|
197
229
|
<% end %>
|
@@ -213,9 +245,9 @@
|
|
213
245
|
</tr>
|
214
246
|
</thead>
|
215
247
|
<tbody>
|
216
|
-
<% @invalid_indexes.each do |
|
248
|
+
<% @invalid_indexes.each do |index| %>
|
217
249
|
<tr>
|
218
|
-
<td><%=
|
250
|
+
<td><%= index[:name] %></td>
|
219
251
|
</tr>
|
220
252
|
<% end %>
|
221
253
|
</tbody>
|
@@ -239,10 +271,10 @@
|
|
239
271
|
<pre>rails g migration remove_unneeded_indexes</pre>
|
240
272
|
<p>And paste</p>
|
241
273
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
|
242
|
-
remove_index <%= query[
|
274
|
+
remove_index <%= query[:unneeded_index][:table].to_sym.inspect %>, name: <%= query[:unneeded_index][:name].to_s.inspect %><% end %></pre>
|
243
275
|
</div>
|
244
276
|
|
245
|
-
<table class="table">
|
277
|
+
<table class="table duplicate-indexes">
|
246
278
|
<thead>
|
247
279
|
<tr>
|
248
280
|
<th>Details</th>
|
@@ -250,14 +282,14 @@ remove_index <%= query["unneeded_index"]["table"].to_sym.inspect %>, name: <%= q
|
|
250
282
|
</thead>
|
251
283
|
<tbody>
|
252
284
|
<% @duplicate_indexes.each do |index| %>
|
253
|
-
<% unneeded_index = index[
|
254
|
-
<% covering_index = index[
|
285
|
+
<% unneeded_index = index[:unneeded_index] %>
|
286
|
+
<% covering_index = index[:covering_index] %>
|
255
287
|
<tr>
|
256
|
-
<td
|
257
|
-
On <%= unneeded_index[
|
258
|
-
<pre><%= unneeded_index[
|
288
|
+
<td>
|
289
|
+
On <%= unneeded_index[:table] %>
|
290
|
+
<pre><%= unneeded_index[:name] %> (<%= unneeded_index[:columns].join(", ") %>)</pre>
|
259
291
|
is covered by
|
260
|
-
<pre><%= covering_index[
|
292
|
+
<pre><%= covering_index[:name] %> (<%= covering_index[:columns].join(", ") %>)</pre>
|
261
293
|
</td>
|
262
294
|
</tr>
|
263
295
|
<% end %>
|
@@ -343,7 +375,7 @@ pg_stat_statements.track = all</pre>
|
|
343
375
|
<pre>rails g migration remove_unused_indexes</pre>
|
344
376
|
<p>And paste</p>
|
345
377
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.each do |query| %>
|
346
|
-
remove_index <%= query[
|
378
|
+
remove_index <%= query[:table].to_sym.inspect %>, name: <%= query[:index].to_s.inspect %><% end %></pre>
|
347
379
|
</div>
|
348
380
|
|
349
381
|
<table class="table">
|
@@ -356,11 +388,15 @@ remove_index <%= query["table"].to_sym.inspect %>, name: <%= query["index"].to_s
|
|
356
388
|
<tbody>
|
357
389
|
<% @unused_indexes.each do |query| %>
|
358
390
|
<tr>
|
359
|
-
<td><%= query[
|
360
|
-
<td><%= query[
|
391
|
+
<td><%= query[:index] %><div class="text-muted">on <%= query[:table] %></div></td>
|
392
|
+
<td><%= query[:size] %></td>
|
361
393
|
</tr>
|
362
394
|
<% end %>
|
363
395
|
</tbody>
|
364
396
|
</table>
|
365
397
|
</div>
|
366
398
|
<% end %>
|
399
|
+
|
400
|
+
<script>
|
401
|
+
highlightQueries();
|
402
|
+
</script>
|
@@ -0,0 +1,69 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<h1>Index Bloat</h1>
|
3
|
+
|
4
|
+
<% if @index_bloat.any? %>
|
5
|
+
|
6
|
+
<p>Indexes can become <%= link_to "bloated over time", "https://www.compose.com/articles/postgresql-bloat-origins-monitoring-and-managing/", target: "_blank" %>. Recreate them to remove bloat.</p>
|
7
|
+
|
8
|
+
<p>For each index, run:</p>
|
9
|
+
<pre><code>CREATE INDEX CONCURRENTLY new_index ...;
|
10
|
+
ANALYZE table;
|
11
|
+
DROP INDEX CONCURRENTLY index;
|
12
|
+
ANALYZE table;
|
13
|
+
ALTER INDEX new_index RENAME TO index;</code></pre>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
<% if @show_sql %>
|
17
|
+
<%= link_to "Hide SQL", {} %>
|
18
|
+
<% else %>
|
19
|
+
<%= link_to "Show SQL", {sql: "t"} %>
|
20
|
+
<% end %>
|
21
|
+
for each index
|
22
|
+
</p>
|
23
|
+
|
24
|
+
<table class="table">
|
25
|
+
<thead>
|
26
|
+
<tr>
|
27
|
+
<th>Index</th>
|
28
|
+
<th style="width: 15%;">Bloat</th>
|
29
|
+
<th style="width: 15%;">Size</th>
|
30
|
+
</tr>
|
31
|
+
</thead>
|
32
|
+
<tbody>
|
33
|
+
<% @index_bloat.each do |index| %>
|
34
|
+
<tr>
|
35
|
+
<td>
|
36
|
+
<span style="word-break: break-all;"><%= index[:index] %></span>
|
37
|
+
</td>
|
38
|
+
<td><%= PgHero.pretty_size(index[:bloat_bytes]) %></td>
|
39
|
+
<td><%= PgHero.pretty_size(index[:index_bytes]) %></td>
|
40
|
+
</tr>
|
41
|
+
<% if @show_sql %>
|
42
|
+
<tr>
|
43
|
+
<td colspan="3" style="border-top: none; padding: 0;">
|
44
|
+
<% new_index = "new_#{index[:index]}".first(63) %>
|
45
|
+
<pre><code><%= index[:definition].sub(" INDEX ", " INDEX CONCURRENTLY \n ").sub(index[:index], new_index) %>;
|
46
|
+
|
47
|
+
ANALYZE <%= pghero_pretty_ident(index[:table], schema: index[:schema]) %>;
|
48
|
+
|
49
|
+
DROP INDEX CONCURRENTLY
|
50
|
+
<%= pghero_pretty_ident(index[:index]) %>;
|
51
|
+
|
52
|
+
ANALYZE <%= pghero_pretty_ident(index[:table], schema: index[:schema]) %>;
|
53
|
+
|
54
|
+
ALTER INDEX <%= pghero_pretty_ident(new_index) %>
|
55
|
+
RENAME TO <%= pghero_pretty_ident(index[:index]) %>;</code></pre>
|
56
|
+
</td>
|
57
|
+
</tr>
|
58
|
+
<% end %>
|
59
|
+
<% end %>
|
60
|
+
</tbody>
|
61
|
+
</table>
|
62
|
+
<% else %>
|
63
|
+
<p>No significant index bloat!</p>
|
64
|
+
<% end %>
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<script>
|
68
|
+
highlightQueries();
|
69
|
+
</script>
|
@@ -13,23 +13,23 @@
|
|
13
13
|
<% @maintenance_info.each do |table| %>
|
14
14
|
<tr>
|
15
15
|
<td>
|
16
|
-
<%= table[
|
17
|
-
<% if table[
|
18
|
-
<span class="text-muted"><%= table[
|
16
|
+
<%= table[:table] %>
|
17
|
+
<% if table[:schema] != "public" %>
|
18
|
+
<span class="text-muted"><%= table[:schema] %></span>
|
19
19
|
<% end %>
|
20
20
|
</td>
|
21
21
|
<td>
|
22
|
-
<% time = [table[
|
22
|
+
<% time = [table[:last_autovacuum], table[:last_vacuum]].compact.max %>
|
23
23
|
<% if time %>
|
24
|
-
<%= @time_zone
|
24
|
+
<%= time.in_time_zone(@time_zone).strftime("%-m/%-e %l:%M %P") %>
|
25
25
|
<% else %>
|
26
26
|
<span class="text-muted">Unknown</span>
|
27
27
|
<% end %>
|
28
28
|
</td>
|
29
29
|
<td>
|
30
|
-
<% time = [table[
|
30
|
+
<% time = [table[:last_autoanalyze], table[:last_analyze]].compact.max %>
|
31
31
|
<% if time %>
|
32
|
-
<%= @time_zone
|
32
|
+
<%= time.in_time_zone(@time_zone).strftime("%-m/%-e %l:%M %P") %>
|
33
33
|
<% else %>
|
34
34
|
<span class="text-muted">Unknown</span>
|
35
35
|
<% end %>
|
@@ -7,6 +7,13 @@
|
|
7
7
|
|
8
8
|
<% if @historical_query_stats_enabled %>
|
9
9
|
<%= render partial: "query_stats_slider" %>
|
10
|
+
<% elsif @database.query_stats_table_exists? && (columns = @database.missing_query_stats_columns).any? %>
|
11
|
+
<div style="clear: both;">
|
12
|
+
<p>Add missing columns to re-enable historical query stats.</p>
|
13
|
+
<pre><code><% @database.missing_query_stats_columns.each do |column| %>ALTER TABLE pghero_query_stats ADD COLUMN "<%= column %>" <%= column == "query_hash" ? "bigint" : "text" %>;
|
14
|
+
<% end %></code></pre>
|
15
|
+
<p>Then restart the web server.</p>
|
16
|
+
</div>
|
10
17
|
<% end %>
|
11
18
|
|
12
19
|
<% if @query_stats_enabled %>
|
@@ -14,6 +21,9 @@
|
|
14
21
|
<div class="alert alert-danger">Cannot understand start or end time.</div>
|
15
22
|
<% elsif @query_stats.any? || @historical_query_stats_enabled %>
|
16
23
|
<%= render partial: "queries_table", locals: {queries: @query_stats, sort_headers: true} %>
|
24
|
+
<script>
|
25
|
+
highlightQueries();
|
26
|
+
</script>
|
17
27
|
<% else %>
|
18
28
|
<p>Stats are not available yet. Come back soon!</p>
|
19
29
|
<% end %>
|