pghero 1.1.4 → 1.2.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/CHANGELOG.md +10 -0
- data/Gemfile +2 -0
- data/app/controllers/pg_hero/home_controller.rb +38 -4
- data/app/views/layouts/pg_hero/application.html.erb +9 -0
- data/app/views/pg_hero/home/_connections_table.html.erb +2 -2
- data/app/views/pg_hero/home/_queries_table.html.erb +10 -0
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +23 -2
- data/app/views/pg_hero/home/_suggested_index.html.erb +1 -0
- data/app/views/pg_hero/home/connections.html.erb +1 -1
- data/app/views/pg_hero/home/explain.html.erb +3 -0
- data/app/views/pg_hero/home/index.html.erb +92 -2
- data/app/views/pg_hero/home/maintenance.html.erb +32 -0
- data/app/views/pg_hero/home/queries.html.erb +4 -0
- data/app/views/pg_hero/home/system.html.erb +3 -0
- data/config/routes.rb +2 -0
- data/guides/Rails.md +17 -0
- data/guides/Suggested-Indexes.md +19 -0
- data/lib/pghero.rb +445 -22
- data/lib/pghero/tasks.rb +5 -0
- data/lib/pghero/version.rb +1 -1
- data/test/best_index_test.rb +131 -0
- data/test/explain_test.rb +18 -0
- data/test/suggested_indexes_test.rb +14 -0
- data/test/test_helper.rb +44 -2
- metadata +12 -4
- data/test/pghero_test.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c032f7f0fd5b3d6484cae193d125d3899ee87594
|
4
|
+
data.tar.gz: 02db1720b5201380dff89ac2dceba86c42d8dc49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f07fe4ece79f34731bb6bc28dc19cd09d365a9f9968caeb0918fb347d9be9d050ce5c1286b09feccc0312d2e379afbf3278fd65ca5c802f4c817e6b1ee7f83c0
|
7
|
+
data.tar.gz: dd334b3251a68372218f765b2c5d986e4bd71172e3a428511f05f4dfb7580123b38421a08eebacc204360cc51b60a9b24bc74dea31889eda9f1d6013bd4bc204
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## 1.2.0
|
2
|
+
|
3
|
+
- Added suggested indexes
|
4
|
+
- Added duplicate indexes
|
5
|
+
- Added maintenance tab
|
6
|
+
- Added load stats for RDS
|
7
|
+
- Added `table_caching` and `index_caching` methods
|
8
|
+
- Added configurable cache hit rate threshold
|
9
|
+
- Show all connections in connections tab
|
10
|
+
|
1
11
|
## 1.1.4
|
2
12
|
|
3
13
|
- Added check for transaction ID wraparound failure
|
data/Gemfile
CHANGED
@@ -11,24 +11,27 @@ module PgHero
|
|
11
11
|
|
12
12
|
def index
|
13
13
|
@title = "Overview"
|
14
|
-
@
|
14
|
+
@query_stats = PgHero.query_stats(historical: true, start_at: 3.hours.ago)
|
15
|
+
@slow_queries = PgHero.slow_queries(query_stats: @query_stats)
|
15
16
|
@long_running_queries = PgHero.long_running_queries
|
16
17
|
@index_hit_rate = PgHero.index_hit_rate
|
17
18
|
@table_hit_rate = PgHero.table_hit_rate
|
18
19
|
@missing_indexes = PgHero.missing_indexes
|
19
20
|
@unused_indexes = PgHero.unused_indexes.select { |q| q["index_scans"].to_i == 0 }
|
20
21
|
@invalid_indexes = PgHero.invalid_indexes
|
21
|
-
@
|
22
|
+
@duplicate_indexes = PgHero.duplicate_indexes
|
23
|
+
@good_cache_rate = @table_hit_rate >= PgHero.cache_hit_rate_threshold.to_f / 100 && @index_hit_rate >= PgHero.cache_hit_rate_threshold.to_f / 100
|
22
24
|
@query_stats_available = PgHero.query_stats_available?
|
23
25
|
@total_connections = PgHero.total_connections
|
24
26
|
@good_total_connections = @total_connections < PgHero.total_connections_threshold
|
25
|
-
@replica = PgHero.replica?
|
26
27
|
if @replica
|
27
28
|
@replication_lag = PgHero.replication_lag
|
28
29
|
@good_replication_lag = @replication_lag < 5
|
29
30
|
end
|
30
31
|
@transaction_id_danger = PgHero.transaction_id_danger
|
31
32
|
@autovacuum_danger = PgHero.autovacuum_danger
|
33
|
+
set_suggested_indexes
|
34
|
+
@show_migrations = PgHero.show_migrations
|
32
35
|
end
|
33
36
|
|
34
37
|
def index_usage
|
@@ -51,6 +54,9 @@ module PgHero
|
|
51
54
|
@title = "Queries"
|
52
55
|
@historical_query_stats_enabled = PgHero.historical_query_stats_enabled?
|
53
56
|
@sort = %w[average_time calls].include?(params[:sort]) ? params[:sort] : nil
|
57
|
+
@min_average_time = params[:min_average_time] ? params[:min_average_time].to_i : nil
|
58
|
+
@min_calls = params[:min_calls] ? params[:min_calls].to_i : nil
|
59
|
+
@debug = params[:debug] == "true"
|
54
60
|
|
55
61
|
@query_stats =
|
56
62
|
begin
|
@@ -62,13 +68,22 @@ module PgHero
|
|
62
68
|
if @historical_query_stats_enabled && !request.xhr?
|
63
69
|
[]
|
64
70
|
else
|
65
|
-
PgHero.query_stats(
|
71
|
+
PgHero.query_stats(
|
72
|
+
historical: true,
|
73
|
+
start_at: @start_at,
|
74
|
+
end_at: @end_at,
|
75
|
+
sort: @sort,
|
76
|
+
min_average_time: @min_average_time,
|
77
|
+
min_calls: @min_calls
|
78
|
+
)
|
66
79
|
end
|
67
80
|
rescue
|
68
81
|
@error = true
|
69
82
|
[]
|
70
83
|
end
|
71
84
|
|
85
|
+
set_suggested_indexes
|
86
|
+
|
72
87
|
if request.xhr?
|
73
88
|
render layout: false, partial: "queries_table", locals: {queries: @query_stats, xhr: true}
|
74
89
|
end
|
@@ -90,6 +105,13 @@ module PgHero
|
|
90
105
|
render json: PgHero.replication_lag_stats
|
91
106
|
end
|
92
107
|
|
108
|
+
def load_stats
|
109
|
+
render json: [
|
110
|
+
{name: "Read IOPS", data: PgHero.read_iops_stats.map { |k, v| [k, v.round] }},
|
111
|
+
{name: "Write IOPS", data: PgHero.write_iops_stats.map { |k, v| [k, v.round] }}
|
112
|
+
]
|
113
|
+
end
|
114
|
+
|
93
115
|
def explain
|
94
116
|
@title = "Explain"
|
95
117
|
@query = params[:query]
|
@@ -98,6 +120,7 @@ module PgHero
|
|
98
120
|
if request.post? && @query
|
99
121
|
begin
|
100
122
|
@explanation = PgHero.explain("#{params[:commit] == "Analyze" ? "ANALYZE " : ""}#{@query}")
|
123
|
+
@suggested_index = PgHero.suggested_indexes(queries: [@query]).first
|
101
124
|
rescue ActiveRecord::StatementInvalid => e
|
102
125
|
@error = e.message
|
103
126
|
end
|
@@ -114,6 +137,11 @@ module PgHero
|
|
114
137
|
@total_connections = PgHero.total_connections
|
115
138
|
end
|
116
139
|
|
140
|
+
def maintenance
|
141
|
+
@title = "Maintenance"
|
142
|
+
@maintenance_info = PgHero.maintenance_info
|
143
|
+
end
|
144
|
+
|
117
145
|
def kill
|
118
146
|
if PgHero.kill(params[:pid])
|
119
147
|
redirect_to root_path, notice: "Query killed"
|
@@ -168,6 +196,12 @@ module PgHero
|
|
168
196
|
def set_query_stats_enabled
|
169
197
|
@query_stats_enabled = PgHero.query_stats_enabled?
|
170
198
|
@system_stats_enabled = PgHero.system_stats_enabled?
|
199
|
+
@replica = PgHero.replica?
|
200
|
+
end
|
201
|
+
|
202
|
+
def set_suggested_indexes
|
203
|
+
@suggested_indexes_by_query = PgHero.suggested_indexes_by_query(query_stats: @query_stats)
|
204
|
+
@suggested_indexes = PgHero.suggested_indexes(suggested_indexes_by_query: @suggested_indexes_by_query)
|
171
205
|
end
|
172
206
|
end
|
173
207
|
end
|
@@ -78,6 +78,12 @@
|
|
78
78
|
padding: 10px;
|
79
79
|
}
|
80
80
|
|
81
|
+
hr {
|
82
|
+
border: none;
|
83
|
+
height: 0;
|
84
|
+
border-top: solid 1px #ddd;
|
85
|
+
}
|
86
|
+
|
81
87
|
.container {
|
82
88
|
max-width: 1000px;
|
83
89
|
margin-left: auto;
|
@@ -414,6 +420,9 @@
|
|
414
420
|
<li class="<%= controller.action_name == "index_usage" ? "active" : "" %>"><%= link_to "Index Usage", index_usage_path %></li>
|
415
421
|
<li class="<%= controller.action_name == "space" ? "active" : "" %>"><%= link_to "Space", space_path %></li>
|
416
422
|
<li class="<%= controller.action_name == "connections" ? "active" : "" %>"><%= link_to "Connections", connections_path %></li>
|
423
|
+
<% unless @replica %>
|
424
|
+
<li class="<%= controller.action_name == "maintenance" ? "active" : "" %>"><%= link_to "Maintenance", maintenance_path %></li>
|
425
|
+
<% end %>
|
417
426
|
<li class="<%= controller.action_name == "live_queries" ? "active" : "" %>"><%= link_to "Live Queries", live_queries_path %></li>
|
418
427
|
<li class="<%= controller.action_name == "explain" ? "active" : "" %>"><%= link_to "Explain", explain_path %></li>
|
419
428
|
<li class="<%= controller.action_name == "tune" ? "active" : "" %>"><%= link_to "Tune", tune_path %></li>
|
@@ -13,9 +13,9 @@
|
|
13
13
|
</tr>
|
14
14
|
</thead>
|
15
15
|
<tbody>
|
16
|
-
<%
|
16
|
+
<% connection_sources.each do |source| %>
|
17
17
|
<tr>
|
18
|
-
<td><%= source["source"] %> <
|
18
|
+
<td><%= source["source"] %> <div class="text-muted"><%= [source["database"], source["ip"]].compact.join(" ") %></div></td>
|
19
19
|
<td><%= number_with_delimiter(source["total_connections"]) %></td>
|
20
20
|
</tr>
|
21
21
|
<% end %>
|
@@ -46,6 +46,16 @@
|
|
46
46
|
<% if query["query"] == "<insufficient privilege>" %>
|
47
47
|
<p class="text-muted">For security reasons, only superusers can see queries executed by other users.</p>
|
48
48
|
<% end %>
|
49
|
+
<% if (i2 = @suggested_indexes_by_query[query["query"]]) %>
|
50
|
+
<% if (index = i2[:index]) && !i2[:covering_index] %>
|
51
|
+
<%= render partial: "suggested_index", locals: {index: index} %>
|
52
|
+
<% end %>
|
53
|
+
<% if @debug %>
|
54
|
+
<code><pre style="color: #f0ad4e; background-color: #333;"><% if i2[:explanation] %><%= i2[:explanation] %><% else %>Rows: <%= i2[:rows] %>
|
55
|
+
Row estimates: <%= i2[:row_estimates].to_a.map { |k, v| "#{k}=#{v}" }.join(", ") %>
|
56
|
+
Row progresssion: <%= i2[:row_progression].to_a.join(", ") %><% end %></pre></code>
|
57
|
+
<% end %>
|
58
|
+
<% end %>
|
49
59
|
</td>
|
50
60
|
</tr>
|
51
61
|
<% end %>
|
@@ -43,6 +43,9 @@
|
|
43
43
|
}
|
44
44
|
|
45
45
|
var sort = <%= @sort.to_json.html_safe %>;
|
46
|
+
var minAverageTime = <%= @min_average_time.to_json %>;
|
47
|
+
var minCalls = <%= @min_calls.to_json %>;
|
48
|
+
var debug = <%= @debug.to_json %>;
|
46
49
|
|
47
50
|
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
48
51
|
|
@@ -100,7 +103,7 @@
|
|
100
103
|
|
101
104
|
function queriesPath(params) {
|
102
105
|
var path = "queries";
|
103
|
-
if (params.start_at || params.end_at || params.sort) {
|
106
|
+
if (params.start_at || params.end_at || params.sort || params.min_average_time || params.min_calls || params.debug) {
|
104
107
|
path += "?" + $.param(params);
|
105
108
|
}
|
106
109
|
return path;
|
@@ -121,14 +124,32 @@
|
|
121
124
|
if (sort) {
|
122
125
|
params.sort = sort;
|
123
126
|
}
|
127
|
+
if (minAverageTime) {
|
128
|
+
params.min_average_time = minAverageTime;
|
129
|
+
}
|
130
|
+
if (minCalls) {
|
131
|
+
params.min_calls = minCalls;
|
132
|
+
}
|
133
|
+
if (debug) {
|
134
|
+
params.debug = debug;
|
135
|
+
}
|
124
136
|
|
125
137
|
var path = queriesPath(params);
|
126
138
|
|
127
139
|
$(".queries-table th a").each( function () {
|
128
|
-
var p = $.extend({}, params, {sort: $(this).data("sort")});
|
140
|
+
var p = $.extend({}, params, {sort: $(this).data("sort"), min_average_time: minAverageTime, min_calls: minCalls, debug: debug});
|
129
141
|
if (!p.sort) {
|
130
142
|
delete p.sort;
|
131
143
|
}
|
144
|
+
if (!p.min_average_time) {
|
145
|
+
delete p.min_average_time;
|
146
|
+
}
|
147
|
+
if (!p.min_calls) {
|
148
|
+
delete p.min_calls;
|
149
|
+
}
|
150
|
+
if (!p.debug) {
|
151
|
+
delete p.debug;
|
152
|
+
}
|
132
153
|
$(this).attr("href", queriesPath(p));
|
133
154
|
});
|
134
155
|
|
@@ -0,0 +1 @@
|
|
1
|
+
<code><pre style="color: #eee; background-color: #333;">CREATE INDEX CONCURRENTLY ON <%= index[:table] %> (<%= index[:columns].join(", ") %>)</pre></code>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="content">
|
2
2
|
<h1>Connections</h1>
|
3
3
|
|
4
|
-
<%= render partial: "connections_table", locals: {total_connections: @total_connections, show_message: false} %>
|
4
|
+
<%= render partial: "connections_table", locals: {total_connections: @total_connections, connection_sources: PgHero.connection_sources(by_database: true), show_message: false} %>
|
5
5
|
</div>
|
@@ -9,6 +9,9 @@
|
|
9
9
|
<% if @explanation %>
|
10
10
|
<pre><%= @explanation %></pre>
|
11
11
|
<p><%= link_to "See how to interpret this", "http://www.postgresql.org/docs/current/static/using-explain.html", target: "_blank" %></p>
|
12
|
+
<% if (index = @suggested_index) %>
|
13
|
+
<%= render partial: "suggested_index", locals: {index: index} %>
|
14
|
+
<% end %>
|
12
15
|
<% elsif @error %>
|
13
16
|
<div class="alert alert-danger"><%= @error %></div>
|
14
17
|
<% end %>
|
@@ -43,9 +43,18 @@
|
|
43
43
|
No long running queries
|
44
44
|
<% end %>
|
45
45
|
</div>
|
46
|
+
<% if PgHero.suggested_indexes_enabled? %>
|
47
|
+
<div class="alert alert-<%= @suggested_indexes.empty? ? "success" : "warning" %>">
|
48
|
+
<% if @suggested_indexes.any? %>
|
49
|
+
<%= pluralize(@suggested_indexes.size, "suggested index", "suggested indexes") %>
|
50
|
+
<% else %>
|
51
|
+
No suggested indexes
|
52
|
+
<% end %>
|
53
|
+
</div>
|
54
|
+
<% end %>
|
46
55
|
<div class="alert alert-<%= @good_cache_rate ? "success" : "warning" %>">
|
47
56
|
<% if @good_cache_rate %>
|
48
|
-
Cache hit rate above
|
57
|
+
Cache hit rate above <%= PgHero.cache_hit_rate_threshold %>%
|
49
58
|
<% else %>
|
50
59
|
Low cache hit rate
|
51
60
|
<% end %>
|
@@ -81,6 +90,13 @@
|
|
81
90
|
No invalid indexes
|
82
91
|
<% end %>
|
83
92
|
</div>
|
93
|
+
<div class="alert alert-<%= @duplicate_indexes.empty? ? "success" : "warning" %>">
|
94
|
+
<% if @duplicate_indexes.any? %>
|
95
|
+
<%= pluralize(@duplicate_indexes.size, "duplicate index", "duplicate indexes") %>
|
96
|
+
<% else %>
|
97
|
+
No duplicate indexes
|
98
|
+
<% end %>
|
99
|
+
</div>
|
84
100
|
<div class="alert alert-<%= @missing_indexes.empty? ? "success" : "warning" %>">
|
85
101
|
<% if @missing_indexes.any? %>
|
86
102
|
<%= pluralize(@missing_indexes.size, "table appears", "tables appear") %> to be missing indexes
|
@@ -114,6 +130,35 @@
|
|
114
130
|
</div>
|
115
131
|
<% end %>
|
116
132
|
|
133
|
+
<% if @suggested_indexes.any? %>
|
134
|
+
<div class="content">
|
135
|
+
<h1>Suggested Indexes</h1>
|
136
|
+
<p>
|
137
|
+
Add indexes to speed up queries.
|
138
|
+
<% if @show_migrations %>
|
139
|
+
Here’s a
|
140
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration3').style.display = 'block';">migration</a> to help.
|
141
|
+
<% end %>
|
142
|
+
</p>
|
143
|
+
|
144
|
+
<div id="migration3" style="display: none;">
|
145
|
+
<pre>rails g migration add_suggested_indexes</pre>
|
146
|
+
<p>And paste</p>
|
147
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @suggested_indexes.each do |index| %>
|
148
|
+
add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(" ,") %>], algorithm: :concurrently<% end %></pre>
|
149
|
+
</div>
|
150
|
+
|
151
|
+
<% @suggested_indexes.each_with_index do |index, i| %>
|
152
|
+
<hr />
|
153
|
+
<%= render partial: "suggested_index", locals: {index: index} %>
|
154
|
+
<p>to speed up</p>
|
155
|
+
<% index[:queries].each do |query| %>
|
156
|
+
<code><pre><%= query %></pre></code>
|
157
|
+
<% end %>
|
158
|
+
<% end %>
|
159
|
+
</div>
|
160
|
+
<% end %>
|
161
|
+
|
117
162
|
<% if !@good_cache_rate %>
|
118
163
|
<div class="content">
|
119
164
|
<h1>Low Cache Hit Rate</h1>
|
@@ -134,7 +179,7 @@
|
|
134
179
|
<% if !@good_total_connections %>
|
135
180
|
<div class="content">
|
136
181
|
<h1>High Number of Connections</h1>
|
137
|
-
<%= render partial: "connections_table", locals: {total_connections: @total_connections, show_message: true} %>
|
182
|
+
<%= render partial: "connections_table", locals: {total_connections: @total_connections, connection_sources: PgHero.connection_sources(by_database: true).first(10), show_message: true} %>
|
138
183
|
</div>
|
139
184
|
<% end %>
|
140
185
|
|
@@ -215,6 +260,49 @@ pg_stat_statements.track = all</pre>
|
|
215
260
|
</div>
|
216
261
|
<% end %>
|
217
262
|
|
263
|
+
<% if @duplicate_indexes.any? %>
|
264
|
+
<div class="content">
|
265
|
+
<h1>Duplicate Indexes</h1>
|
266
|
+
|
267
|
+
<p>
|
268
|
+
These indexes exist, but aren’t needed. Remove them
|
269
|
+
<% if @show_migrations %>
|
270
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration2').style.display = 'block';">with a migration</a>
|
271
|
+
<% end %>
|
272
|
+
for faster writes.
|
273
|
+
</p>
|
274
|
+
|
275
|
+
<div id="migration2" style="display: none;">
|
276
|
+
<pre>rails g migration remove_unneeded_indexes</pre>
|
277
|
+
<p>And paste</p>
|
278
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
|
279
|
+
remove_index <%= query["unneeded_index"]["table"].to_sym.inspect %>, name: <%= query["unneeded_index"]["name"].to_s.inspect %><% end %></pre>
|
280
|
+
</div>
|
281
|
+
|
282
|
+
<table class="table">
|
283
|
+
<thead>
|
284
|
+
<tr>
|
285
|
+
<th>Details</th>
|
286
|
+
</tr>
|
287
|
+
</thead>
|
288
|
+
<tbody>
|
289
|
+
<% @duplicate_indexes.each do |index| %>
|
290
|
+
<% unneeded_index = index["unneeded_index"] %>
|
291
|
+
<% covering_index = index["covering_index"] %>
|
292
|
+
<tr>
|
293
|
+
<td style="padding-top: 15px; padding-bottom: 5px;">
|
294
|
+
On <%= unneeded_index["table"] %>
|
295
|
+
<pre><%= unneeded_index["name"] %> (<%= unneeded_index["columns"].join(", ") %>)</pre>
|
296
|
+
is covered by
|
297
|
+
<pre><%= covering_index["name"] %> (<%= covering_index["columns"].join(", ") %>)</pre>
|
298
|
+
</td>
|
299
|
+
</tr>
|
300
|
+
<% end %>
|
301
|
+
</tbody>
|
302
|
+
</table>
|
303
|
+
</div>
|
304
|
+
<% end %>
|
305
|
+
|
218
306
|
<% if @missing_indexes.any? %>
|
219
307
|
<div class="content">
|
220
308
|
<h1>Missing Indexes</h1>
|
@@ -248,7 +336,9 @@ pg_stat_statements.track = all</pre>
|
|
248
336
|
|
249
337
|
<p>
|
250
338
|
Unused indexes cause unnecessary overhead. Remove them
|
339
|
+
<% if @show_migrations %>
|
251
340
|
<a href="javascript: void(0);" onclick="document.getElementById('migration').style.display = 'block';">with a migration</a>
|
341
|
+
<% end %>
|
252
342
|
for faster writes.
|
253
343
|
</p>
|
254
344
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<h1>Maintenance</h1>
|
3
|
+
|
4
|
+
<table class="table">
|
5
|
+
<thead>
|
6
|
+
<tr>
|
7
|
+
<th>Table</th>
|
8
|
+
<th style="width: 20%;">Last Vacuum</th>
|
9
|
+
<th style="width: 20%;">Last Analyze</th>
|
10
|
+
</tr>
|
11
|
+
</thead>
|
12
|
+
<tbody>
|
13
|
+
<% @maintenance_info.each do |table| %>
|
14
|
+
<tr>
|
15
|
+
<td><%= table["table"] %></td>
|
16
|
+
<td>
|
17
|
+
<% time = [table["last_autovacuum"], table["last_vacuum"]].compact.max %>
|
18
|
+
<% if time %>
|
19
|
+
<%= PgHero.time_zone.parse(time).strftime("%m/%-e %l:%M %P") %>
|
20
|
+
<% end %>
|
21
|
+
</td>
|
22
|
+
<td>
|
23
|
+
<% time = [table["last_autoanalyze"], table["last_analyze"]].compact.max %>
|
24
|
+
<% if time %>
|
25
|
+
<%= PgHero.time_zone.parse(time).strftime("%m/%-e %l:%M %P") %>
|
26
|
+
<% end %>
|
27
|
+
</td>
|
28
|
+
</tr>
|
29
|
+
<% end %>
|
30
|
+
</tbody>
|
31
|
+
</table>
|
32
|
+
</div>
|
@@ -9,6 +9,10 @@
|
|
9
9
|
<%= render partial: "query_stats_slider" %>
|
10
10
|
<% end %>
|
11
11
|
|
12
|
+
<% if PgHero.suggested_indexes_enabled? %>
|
13
|
+
<p style="text-align: center; margin-top: 7px;">PgHero now suggests indexes. <%= link_to "See how it thinks", {debug: true} %>.</p>
|
14
|
+
<% end %>
|
15
|
+
|
12
16
|
<% if @query_stats_enabled %>
|
13
17
|
<% if @error %>
|
14
18
|
<div class="alert alert-danger">Cannot understand start or end time.</div>
|