pghero 2.8.3 → 3.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/LICENSE.txt +1 -1
- data/app/assets/javascripts/pghero/Chart.bundle.js +23379 -19766
- data/app/assets/javascripts/pghero/application.js +13 -12
- data/app/assets/javascripts/pghero/chartkick.js +834 -764
- data/app/assets/javascripts/pghero/highlight.min.js +440 -0
- data/app/assets/javascripts/pghero/jquery.js +318 -197
- data/app/assets/javascripts/pghero/nouislider.js +676 -1066
- data/app/assets/stylesheets/pghero/application.css +8 -2
- data/app/assets/stylesheets/pghero/nouislider.css +4 -10
- data/app/controllers/pg_hero/home_controller.rb +103 -37
- data/app/helpers/pg_hero/home_helper.rb +2 -2
- data/app/views/layouts/pg_hero/application.html.erb +4 -2
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +6 -6
- data/app/views/pg_hero/home/connections.html.erb +6 -6
- data/app/views/pg_hero/home/explain.html.erb +4 -2
- data/app/views/pg_hero/home/index.html.erb +3 -1
- data/app/views/pg_hero/home/queries.html.erb +4 -2
- data/app/views/pg_hero/home/relation_space.html.erb +1 -1
- data/app/views/pg_hero/home/show_query.html.erb +17 -13
- data/app/views/pg_hero/home/space.html.erb +44 -40
- data/app/views/pg_hero/home/system.html.erb +6 -6
- data/lib/generators/pghero/query_stats_generator.rb +1 -0
- data/lib/generators/pghero/space_stats_generator.rb +1 -0
- data/lib/generators/pghero/templates/config.yml.tt +6 -0
- data/lib/pghero/database.rb +0 -7
- data/lib/pghero/engine.rb +1 -1
- data/lib/pghero/methods/basic.rb +6 -9
- data/lib/pghero/methods/connections.rb +5 -5
- data/lib/pghero/methods/constraints.rb +1 -1
- data/lib/pghero/methods/explain.rb +34 -0
- data/lib/pghero/methods/indexes.rb +8 -8
- data/lib/pghero/methods/kill.rb +1 -1
- data/lib/pghero/methods/maintenance.rb +4 -4
- data/lib/pghero/methods/queries.rb +2 -2
- data/lib/pghero/methods/query_stats.rb +28 -29
- data/lib/pghero/methods/replication.rb +2 -2
- data/lib/pghero/methods/sequences.rb +3 -3
- data/lib/pghero/methods/settings.rb +1 -1
- data/lib/pghero/methods/space.rb +20 -14
- data/lib/pghero/methods/suggested_indexes.rb +40 -110
- data/lib/pghero/methods/system.rb +16 -16
- data/lib/pghero/methods/tables.rb +4 -5
- data/lib/pghero/methods/users.rb +12 -4
- data/lib/pghero/version.rb +1 -1
- data/lib/pghero.rb +90 -65
- data/lib/tasks/pghero.rake +11 -1
- data/licenses/LICENSE-chart.js.txt +1 -1
- data/licenses/LICENSE-date-fns.txt +21 -20
- data/licenses/LICENSE-kurkle-color.txt +9 -0
- metadata +9 -8
- data/app/assets/javascripts/pghero/highlight.pack.js +0 -2
@@ -187,7 +187,7 @@ hr {
|
|
187
187
|
}
|
188
188
|
|
189
189
|
#slider-container {
|
190
|
-
padding: 6px
|
190
|
+
padding: 6px 8px 14px 8px;
|
191
191
|
}
|
192
192
|
|
193
193
|
.queries-table th a, .space-table th a {
|
@@ -195,7 +195,7 @@ hr {
|
|
195
195
|
}
|
196
196
|
|
197
197
|
#slider {
|
198
|
-
margin
|
198
|
+
margin: 0px 14px 18px 14px;
|
199
199
|
}
|
200
200
|
|
201
201
|
#range-start {
|
@@ -485,6 +485,12 @@ body {
|
|
485
485
|
color: #999;
|
486
486
|
}
|
487
487
|
|
488
|
+
.pie-chart {
|
489
|
+
height: 260px;
|
490
|
+
line-height: 260px;
|
491
|
+
margin-bottom: 20px;
|
492
|
+
}
|
493
|
+
|
488
494
|
.unused-index {
|
489
495
|
color: #f0ad4e;
|
490
496
|
font-size: 11px;
|
@@ -1,4 +1,3 @@
|
|
1
|
-
/*! nouislider - 14.6.1 - 8/17/2020 */
|
2
1
|
/* Functional styling;
|
3
2
|
* These styles are required for noUiSlider to function.
|
4
3
|
* You don't need to change these rules to apply your design.
|
@@ -39,20 +38,14 @@
|
|
39
38
|
z-index: 1;
|
40
39
|
top: 0;
|
41
40
|
right: 0;
|
41
|
+
height: 100%;
|
42
|
+
width: 100%;
|
42
43
|
-ms-transform-origin: 0 0;
|
43
44
|
-webkit-transform-origin: 0 0;
|
44
45
|
-webkit-transform-style: preserve-3d;
|
45
46
|
transform-origin: 0 0;
|
46
47
|
transform-style: flat;
|
47
48
|
}
|
48
|
-
.noUi-connect {
|
49
|
-
height: 100%;
|
50
|
-
width: 100%;
|
51
|
-
}
|
52
|
-
.noUi-origin {
|
53
|
-
height: 10%;
|
54
|
-
width: 10%;
|
55
|
-
}
|
56
49
|
/* Offset direction
|
57
50
|
*/
|
58
51
|
.noUi-txt-dir-rtl.noUi-horizontal .noUi-origin {
|
@@ -63,6 +56,7 @@
|
|
63
56
|
* connect elements.
|
64
57
|
*/
|
65
58
|
.noUi-vertical .noUi-origin {
|
59
|
+
top: -100%;
|
66
60
|
width: 0;
|
67
61
|
}
|
68
62
|
.noUi-horizontal .noUi-origin {
|
@@ -103,7 +97,7 @@
|
|
103
97
|
width: 28px;
|
104
98
|
height: 34px;
|
105
99
|
right: -6px;
|
106
|
-
|
100
|
+
bottom: -17px;
|
107
101
|
}
|
108
102
|
.noUi-txt-dir-rtl.noUi-horizontal .noUi-handle {
|
109
103
|
left: -17px;
|
@@ -46,11 +46,18 @@ module PgHero
|
|
46
46
|
|
47
47
|
@transaction_id_danger = @database.transaction_id_danger(threshold: 1500000000)
|
48
48
|
|
49
|
-
|
49
|
+
sequences, @sequences_timeout = rescue_timeout([]) { @database.sequences }
|
50
|
+
@readable_sequences, @unreadable_sequences = sequences.partition { |s| s[:readable] }
|
50
51
|
|
51
52
|
@sequence_danger = @database.sequence_danger(threshold: (params[:sequence_threshold] || 0.9).to_f, sequences: @readable_sequences)
|
52
53
|
|
53
|
-
@indexes =
|
54
|
+
@indexes, @indexes_timeout =
|
55
|
+
if @sequences_timeout
|
56
|
+
# skip indexes for faster loading
|
57
|
+
[[], true]
|
58
|
+
else
|
59
|
+
rescue_timeout([]) { @database.indexes }
|
60
|
+
end
|
54
61
|
@invalid_indexes = @database.invalid_indexes(indexes: @indexes)
|
55
62
|
@invalid_constraints = @database.invalid_constraints
|
56
63
|
@duplicate_indexes = @database.duplicate_indexes(indexes: @indexes)
|
@@ -80,11 +87,11 @@ module PgHero
|
|
80
87
|
@days = (params[:days] || 7).to_i
|
81
88
|
@database_size = @database.database_size
|
82
89
|
@only_tables = params[:tables].present?
|
83
|
-
@relation_sizes = @only_tables ? @database.table_sizes : @database.relation_sizes
|
90
|
+
@relation_sizes, @sizes_timeout = rescue_timeout([]) { @only_tables ? @database.table_sizes : @database.relation_sizes }
|
84
91
|
@space_stats_enabled = @database.space_stats_enabled? && !@only_tables
|
85
92
|
if @space_stats_enabled
|
86
93
|
space_growth = @database.space_growth(days: @days, relation_sizes: @relation_sizes)
|
87
|
-
@growth_bytes_by_relation =
|
94
|
+
@growth_bytes_by_relation = space_growth.to_h { |r| [[r[:schema], r[:relation]], r[:growth_bytes]] }
|
88
95
|
if params[:sort] == "growth"
|
89
96
|
@relation_sizes.sort_by! { |r| s = @growth_bytes_by_relation[[r[:schema], r[:relation]]]; [s ? 0 : 1, -s.to_i, r[:schema], r[:relation]] }
|
90
97
|
end
|
@@ -157,8 +164,9 @@ module PgHero
|
|
157
164
|
)
|
158
165
|
end
|
159
166
|
|
160
|
-
|
161
|
-
|
167
|
+
if !@historical_query_stats_enabled || request.xhr?
|
168
|
+
set_suggested_indexes
|
169
|
+
end
|
162
170
|
|
163
171
|
# fix back button issue with caching
|
164
172
|
response.headers["Cache-Control"] = "must-revalidate, no-store, no-cache, private"
|
@@ -184,7 +192,7 @@ module PgHero
|
|
184
192
|
@chart2_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at].change(sec: 0), r[:average_time].round(1)] }, library: chart_library_options}]
|
185
193
|
@chart3_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at].change(sec: 0), r[:calls]] }, library: chart_library_options}]
|
186
194
|
|
187
|
-
@origins =
|
195
|
+
@origins = query_hash_stats.group_by { |r| r[:origin].to_s }.to_h { |k, v| [k, v.size] }
|
188
196
|
@total_count = query_hash_stats.size
|
189
197
|
end
|
190
198
|
|
@@ -192,11 +200,12 @@ module PgHero
|
|
192
200
|
@tables.sort!
|
193
201
|
|
194
202
|
if @tables.any?
|
195
|
-
@row_counts =
|
196
|
-
@
|
203
|
+
@row_counts = @database.table_stats(table: @tables).to_h { |i| [i[:table], i[:estimated_rows]] }
|
204
|
+
indexes, @indexes_timeout = rescue_timeout([]) { @database.indexes }
|
205
|
+
@indexes_by_table = indexes.group_by { |i| i[:table] }
|
197
206
|
end
|
198
207
|
else
|
199
|
-
render_text "Unknown query"
|
208
|
+
render_text "Unknown query", status: :not_found
|
200
209
|
end
|
201
210
|
end
|
202
211
|
|
@@ -217,9 +226,9 @@ module PgHero
|
|
217
226
|
@period = (params[:period] || 60.seconds).to_i
|
218
227
|
|
219
228
|
if @duration / @period > 1440
|
220
|
-
render_text "Too many data points"
|
229
|
+
render_text "Too many data points", status: :bad_request
|
221
230
|
elsif @period % 60 != 0
|
222
|
-
render_text "Period must be a multiple of 60"
|
231
|
+
render_text "Period must be a multiple of 60", status: :bad_request
|
223
232
|
end
|
224
233
|
end
|
225
234
|
|
@@ -239,9 +248,16 @@ module PgHero
|
|
239
248
|
stats =
|
240
249
|
case @database.system_stats_provider
|
241
250
|
when :azure
|
242
|
-
|
243
|
-
|
244
|
-
|
251
|
+
if @database.send(:azure_flexible_server?)
|
252
|
+
[
|
253
|
+
{name: "Read IOPS", data: @database.read_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options},
|
254
|
+
{name: "Write IOPS", data: @database.write_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options}
|
255
|
+
]
|
256
|
+
else
|
257
|
+
[
|
258
|
+
{name: "IO Consumption", data: @database.azure_stats("io_consumption_percent", **system_params), library: chart_library_options}
|
259
|
+
]
|
260
|
+
end
|
245
261
|
when :gcp
|
246
262
|
[
|
247
263
|
{name: "Read Ops", data: @database.read_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options},
|
@@ -263,30 +279,57 @@ module PgHero
|
|
263
279
|
end
|
264
280
|
|
265
281
|
def explain
|
282
|
+
unless @explain_enabled
|
283
|
+
render_text "Explain not enabled", status: :bad_request
|
284
|
+
return
|
285
|
+
end
|
286
|
+
|
266
287
|
@title = "Explain"
|
267
288
|
@query = params[:query]
|
289
|
+
@explain_analyze_enabled = PgHero.explain_mode == "analyze"
|
290
|
+
|
268
291
|
# TODO use get + token instead of post so users can share links
|
269
292
|
# need to prevent CSRF and DoS
|
270
|
-
if request.post? && @query
|
293
|
+
if request.post? && @query.present?
|
271
294
|
begin
|
272
|
-
|
295
|
+
explain_options =
|
273
296
|
case params[:commit]
|
274
297
|
when "Analyze"
|
275
|
-
|
298
|
+
{analyze: true}
|
276
299
|
when "Visualize"
|
277
|
-
|
300
|
+
if @explain_analyze_enabled
|
301
|
+
{analyze: true, costs: true, verbose: true, buffers: true, format: "json"}
|
302
|
+
else
|
303
|
+
{costs: true, verbose: true, format: "json"}
|
304
|
+
end
|
278
305
|
else
|
279
|
-
|
306
|
+
{}
|
280
307
|
end
|
281
|
-
|
308
|
+
|
309
|
+
if explain_options[:analyze] && !@explain_analyze_enabled
|
310
|
+
render_text "Explain analyze not enabled", status: :bad_request
|
311
|
+
return
|
312
|
+
end
|
313
|
+
|
314
|
+
@explanation = @database.explain_v2(@query, **explain_options)
|
282
315
|
@suggested_index = @database.suggested_indexes(queries: [@query]).first if @database.suggested_indexes_enabled?
|
283
316
|
@visualize = params[:commit] == "Visualize"
|
284
317
|
rescue ActiveRecord::StatementInvalid => e
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
318
|
+
message = e.message
|
319
|
+
@error =
|
320
|
+
if message == "Unsafe statement"
|
321
|
+
"Unsafe statement"
|
322
|
+
elsif message.start_with?("PG::ProtocolViolation: ERROR: bind message supplies 0 parameters")
|
323
|
+
"Can't explain queries with bind parameters"
|
324
|
+
elsif message.start_with?("PG::SyntaxError")
|
325
|
+
"Syntax error with query"
|
326
|
+
elsif message.start_with?("PG::QueryCanceled")
|
327
|
+
"Query timed out"
|
328
|
+
else
|
329
|
+
# default to a generic message
|
330
|
+
# since data can be extracted through the Postgres error message
|
331
|
+
"Error explaining query"
|
332
|
+
end
|
290
333
|
end
|
291
334
|
end
|
292
335
|
end
|
@@ -368,11 +411,20 @@ module PgHero
|
|
368
411
|
redirect_backward alert: "The database user does not have permission to enable query stats"
|
369
412
|
end
|
370
413
|
|
414
|
+
# TODO disable if historical query stats enabled?
|
371
415
|
def reset_query_stats
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
416
|
+
success =
|
417
|
+
if @database.server_version_num >= 120000
|
418
|
+
@database.reset_query_stats
|
419
|
+
else
|
420
|
+
@database.reset_instance_query_stats
|
421
|
+
end
|
422
|
+
|
423
|
+
if success
|
424
|
+
redirect_backward notice: "Query stats reset"
|
425
|
+
else
|
426
|
+
redirect_backward alert: "The database user does not have permission to reset query stats"
|
427
|
+
end
|
376
428
|
end
|
377
429
|
|
378
430
|
protected
|
@@ -401,17 +453,22 @@ module PgHero
|
|
401
453
|
@query_stats_enabled = @database.query_stats_enabled?
|
402
454
|
@system_stats_enabled = @database.system_stats_enabled?
|
403
455
|
@replica = @database.replica?
|
456
|
+
@explain_enabled = PgHero.explain_enabled?
|
404
457
|
end
|
405
458
|
|
406
459
|
def set_suggested_indexes(min_average_time = 0, min_calls = 0)
|
460
|
+
if @database.suggested_indexes_enabled? && !@indexes
|
461
|
+
@indexes, @indexes_timeout = rescue_timeout([]) { @database.indexes }
|
462
|
+
end
|
463
|
+
|
407
464
|
@suggested_indexes_by_query =
|
408
|
-
if @database.suggested_indexes_enabled?
|
409
|
-
@database.suggested_indexes_by_query(query_stats: @query_stats.select { |qs| qs[:average_time] >= min_average_time && qs[:calls] >= min_calls })
|
465
|
+
if !@indexes_timeout && @database.suggested_indexes_enabled?
|
466
|
+
@database.suggested_indexes_by_query(query_stats: @query_stats.select { |qs| qs[:average_time] >= min_average_time && qs[:calls] >= min_calls }, indexes: @indexes)
|
410
467
|
else
|
411
468
|
{}
|
412
469
|
end
|
413
470
|
|
414
|
-
@suggested_indexes = @database.suggested_indexes(suggested_indexes_by_query: @suggested_indexes_by_query
|
471
|
+
@suggested_indexes = @database.suggested_indexes(suggested_indexes_by_query: @suggested_indexes_by_query)
|
415
472
|
@query_stats_by_query = @query_stats.index_by { |q| q[:query] }
|
416
473
|
@debug = params[:debug].present?
|
417
474
|
end
|
@@ -445,12 +502,13 @@ module PgHero
|
|
445
502
|
end
|
446
503
|
|
447
504
|
def check_api
|
448
|
-
|
505
|
+
if Rails.application.config.try(:api_only)
|
506
|
+
render_text "No support for Rails API. See https://github.com/pghero/pghero for a standalone app.", status: :internal_server_error
|
507
|
+
end
|
449
508
|
end
|
450
509
|
|
451
|
-
|
452
|
-
|
453
|
-
render plain: message
|
510
|
+
def render_text(message, status:)
|
511
|
+
render plain: message, status: status
|
454
512
|
end
|
455
513
|
|
456
514
|
def ensure_query_stats
|
@@ -458,5 +516,13 @@ module PgHero
|
|
458
516
|
redirect_to root_path, alert: "Query stats not enabled"
|
459
517
|
end
|
460
518
|
end
|
519
|
+
|
520
|
+
# rescue QueryCanceled for case when
|
521
|
+
# statement timeout is less than lock timeout
|
522
|
+
def rescue_timeout(default)
|
523
|
+
[yield, false]
|
524
|
+
rescue ActiveRecord::LockWaitTimeout, ActiveRecord::QueryCanceled
|
525
|
+
[default, true]
|
526
|
+
end
|
461
527
|
end
|
462
528
|
end
|
@@ -12,8 +12,8 @@ module PgHero
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
15
|
+
def pghero_js_value(value)
|
16
|
+
json_escape(value.to_json(root: false)).html_safe
|
17
17
|
end
|
18
18
|
|
19
19
|
def pghero_remove_index(query)
|
@@ -7,7 +7,7 @@
|
|
7
7
|
<%= favicon_link_tag "pghero/favicon.png" %>
|
8
8
|
<% if defined?(Propshaft::Railtie) %>
|
9
9
|
<%= stylesheet_link_tag "pghero/nouislider", "pghero/arduino-light", "pghero/application" %>
|
10
|
-
<%= javascript_include_tag "pghero/jquery", "pghero/nouislider", "pghero/Chart.bundle", "pghero/chartkick", "pghero/highlight.
|
10
|
+
<%= javascript_include_tag "pghero/jquery", "pghero/nouislider", "pghero/Chart.bundle", "pghero/chartkick", "pghero/highlight.min", "pghero/application" %>
|
11
11
|
<% else %>
|
12
12
|
<%= stylesheet_link_tag "pghero/application" %>
|
13
13
|
<%= javascript_include_tag "pghero/application" %>
|
@@ -48,7 +48,9 @@
|
|
48
48
|
<% unless @database.replica? %>
|
49
49
|
<li class="<%= controller.action_name == "maintenance" ? "active" : "" %>"><%= link_to "Maintenance", maintenance_path %></li>
|
50
50
|
<% end %>
|
51
|
-
|
51
|
+
<% if @explain_enabled %>
|
52
|
+
<li class="<%= controller.action_name == "explain" ? "active" : "" %>"><%= link_to "Explain", explain_path %></li>
|
53
|
+
<% end %>
|
52
54
|
<li class="<%= controller.action_name == "tune" ? "active" : "" %>"><%= link_to "Tune", tune_path %></li>
|
53
55
|
</ul>
|
54
56
|
|
@@ -5,12 +5,12 @@
|
|
5
5
|
</div>
|
6
6
|
|
7
7
|
<script>
|
8
|
-
<%=
|
9
|
-
<%=
|
10
|
-
<%=
|
11
|
-
<%=
|
12
|
-
<%=
|
13
|
-
<%=
|
8
|
+
var sort = <%= pghero_js_value(@sort) %>;
|
9
|
+
var minAverageTime = <%= pghero_js_value(@min_average_time) %>;
|
10
|
+
var minCalls = <%= pghero_js_value(@min_calls) %>;
|
11
|
+
var debug = <%= pghero_js_value(@debug) %>;
|
12
|
+
var startAt = <%= pghero_js_value(params[:start_at] ? @start_at.to_i * 1000 : nil) %>;
|
13
|
+
var endAt = <%= pghero_js_value(@end_at.to_i * 1000) %>;
|
14
14
|
|
15
15
|
initSlider();
|
16
16
|
</script>
|
@@ -6,24 +6,24 @@
|
|
6
6
|
<% if @total_connections > 0 %>
|
7
7
|
<h3>By Database</h3>
|
8
8
|
|
9
|
-
<div id="chart-1" class="chart
|
9
|
+
<div id="chart-1" class="chart pie-chart">Loading...</div>
|
10
10
|
<script>
|
11
|
-
new Chartkick.PieChart("chart-1", <%=
|
11
|
+
new Chartkick.PieChart("chart-1", <%= pghero_js_value(@connections_by_database) %>);
|
12
12
|
</script>
|
13
13
|
|
14
14
|
<h3>By User</h3>
|
15
15
|
|
16
|
-
<div id="chart-2" class="chart
|
16
|
+
<div id="chart-2" class="chart pie-chart">Loading...</div>
|
17
17
|
<script>
|
18
|
-
new Chartkick.PieChart("chart-2", <%=
|
18
|
+
new Chartkick.PieChart("chart-2", <%= pghero_js_value(@connections_by_user) %>);
|
19
19
|
</script>
|
20
20
|
|
21
21
|
<% if @connections_by_ssl_status %>
|
22
22
|
<h3>By Security</h3>
|
23
23
|
|
24
|
-
<div id="chart-3" class="chart
|
24
|
+
<div id="chart-3" class="chart pie-chart">Loading...</div>
|
25
25
|
<script>
|
26
|
-
new Chartkick.PieChart("chart-3", <%=
|
26
|
+
new Chartkick.PieChart("chart-3", <%= pghero_js_value(@connections_by_ssl_status) %>);
|
27
27
|
</script>
|
28
28
|
<% end %>
|
29
29
|
|
@@ -5,14 +5,16 @@
|
|
5
5
|
<div class="field"><%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %></div>
|
6
6
|
<p>
|
7
7
|
<%= submit_tag "Explain", class: "btn btn-info", style: "margin-right: 10px;" %>
|
8
|
-
|
8
|
+
<% if @explain_analyze_enabled %>
|
9
|
+
<%= submit_tag "Analyze", class: "btn btn-danger", style: "margin-right: 10px;" %>
|
10
|
+
<% end %>
|
9
11
|
<%= submit_tag "Visualize", class: "btn btn-danger" %>
|
10
12
|
</p>
|
11
13
|
<% end %>
|
12
14
|
|
13
15
|
<% if @explanation %>
|
14
16
|
<% if @visualize %>
|
15
|
-
<p>Paste the output below into the <%= link_to "
|
17
|
+
<p>Paste the output below into the <%= link_to "explain visualizer", PgHero.visualize_url, target: "_blank" %></p>
|
16
18
|
<% end %>
|
17
19
|
<pre><code><%= @explanation %></code></pre>
|
18
20
|
<% unless @visualize %>
|
@@ -55,9 +55,11 @@
|
|
55
55
|
Vacuuming healthy
|
56
56
|
<% end %>
|
57
57
|
</div>
|
58
|
-
<div class="alert alert-<%= @sequence_danger && @sequence_danger.empty? ? "success" : "warning" %>">
|
58
|
+
<div class="alert alert-<%= @sequence_danger && @sequence_danger.empty? && !@sequences_timeout ? "success" : "warning" %>">
|
59
59
|
<% if @sequence_danger.any? %>
|
60
60
|
<%= pluralize(@sequence_danger.size, "column") %> approaching overflow
|
61
|
+
<% elsif @sequences_timeout %>
|
62
|
+
Sequences not available (system catalog locked)
|
61
63
|
<% else %>
|
62
64
|
No columns near integer overflow
|
63
65
|
<% end %>
|
@@ -1,9 +1,11 @@
|
|
1
1
|
<div class="content">
|
2
|
-
<% if @query_stats_enabled %>
|
2
|
+
<% if @query_stats_enabled && !@historical_query_stats_enabled %>
|
3
3
|
<%= button_to "Reset", reset_query_stats_path, class: "btn btn-danger", style: "float: right;" %>
|
4
4
|
<% end %>
|
5
5
|
|
6
|
-
|
6
|
+
<% if !@historical_query_stats_enabled %>
|
7
|
+
<h1 style="float: left;">Queries</h1>
|
8
|
+
<% end %>
|
7
9
|
|
8
10
|
<% if @historical_query_stats_enabled %>
|
9
11
|
<%= render partial: "query_stats_slider" %>
|
@@ -9,6 +9,6 @@
|
|
9
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", <%=
|
12
|
+
new Chartkick.LineChart("chart-1", <%= pghero_js_value(@chart_data) %>, {colors: ["#5bc0de"], legend: false, min: null, bytes: true, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
13
13
|
</script>
|
14
14
|
</div>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
highlightQueries()
|
5
5
|
</script>
|
6
6
|
|
7
|
-
<% if @explainable_query %>
|
7
|
+
<% if @explain_enabled && @explainable_query %>
|
8
8
|
<p>
|
9
9
|
<%= button_to "Explain", explain_path, params: {query: @explainable_query}, form: {target: "_blank"}, class: "btn btn-info" %>
|
10
10
|
</p>
|
@@ -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", <%=
|
52
|
+
new Chartkick.LineChart("chart-1", <%= pghero_js_value(@chart_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {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", <%=
|
58
|
+
new Chartkick.LineChart("chart-2", <%= pghero_js_value(@chart2_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {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", <%=
|
64
|
+
new Chartkick.LineChart("chart-3", <%= pghero_js_value(@chart3_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
65
65
|
</script>
|
66
66
|
<% else %>
|
67
67
|
<p>
|
@@ -88,15 +88,19 @@
|
|
88
88
|
<td><%= table %></td>
|
89
89
|
<td><%= @row_counts[table] %></td>
|
90
90
|
<td>
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
91
|
+
<% if @indexes_timeout %>
|
92
|
+
Not available
|
93
|
+
<% else %>
|
94
|
+
<ul>
|
95
|
+
<% @indexes_by_table[table].to_a.sort_by { |i| [i[:primary] ? 0 : 1, i[:columns]] }.each do |i3| %>
|
96
|
+
<li>
|
97
|
+
<%= i3[:columns].join(", ") %><% if i3[:using] != "btree" %>
|
98
|
+
<%= i3[:using].to_s.upcase %><% end %>
|
99
|
+
<% if i3[:primary] %> PRIMARY<% elsif i3[:unique] %> UNIQUE<% end %>
|
100
|
+
</li>
|
101
|
+
<% end %>
|
102
|
+
</ul>
|
103
|
+
<% end %>
|
100
104
|
</td>
|
101
105
|
</tr>
|
102
106
|
<% end %>
|
@@ -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", <%=
|
9
|
+
new Chartkick.LineChart("chart-1", <%= pghero_js_value(free_space_stats_path) %>, {colors: ["#5bc0de"], bytes: true, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
10
10
|
</script>
|
11
11
|
<% end %>
|
12
12
|
|
@@ -37,47 +37,51 @@
|
|
37
37
|
</div>
|
38
38
|
<% end %>
|
39
39
|
|
40
|
-
|
41
|
-
<
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
<% if @space_stats_enabled %>
|
46
|
-
<th style="width: 15%;"><%= link_to "#{@days}d Growth", @header_options.merge(sort: "growth") %></th>
|
47
|
-
<% end %>
|
48
|
-
</tr>
|
49
|
-
</thead>
|
50
|
-
<tbody>
|
51
|
-
<% @relation_sizes.each do |query| %>
|
40
|
+
<% if @sizes_timeout %>
|
41
|
+
<p>Breakdown not available (system catalog locked)</p>
|
42
|
+
<% else %>
|
43
|
+
<table class="table space-table">
|
44
|
+
<thead>
|
52
45
|
<tr>
|
53
|
-
<
|
54
|
-
|
55
|
-
<% name = query[:relation] || query[:table] %>
|
56
|
-
<% if @space_stats_enabled %>
|
57
|
-
<%= link_to name, relation_space_path(name, schema: query[:schema]), target: "_blank", style: "color: inherit;" %>
|
58
|
-
<% else %>
|
59
|
-
<%= name %>
|
60
|
-
<% end %>
|
61
|
-
</span>
|
62
|
-
<% if query[:schema] != "public" %>
|
63
|
-
<span class="text-muted"><%= query[:schema] %></span>
|
64
|
-
<% end %>
|
65
|
-
<% if @unused_index_names.include?(query[:relation]) %>
|
66
|
-
<span class="unused-index">UNUSED</span>
|
67
|
-
<% end %>
|
68
|
-
</td>
|
69
|
-
<td><%= query[:size] %></td>
|
46
|
+
<th><%= link_to (@only_tables ? "Table" : "Relation"), @header_options.merge(sort: "name") %></th>
|
47
|
+
<th style="width: 15%;"><%= link_to "Size", @header_options %></th>
|
70
48
|
<% if @space_stats_enabled %>
|
71
|
-
<
|
72
|
-
<% if @growth_bytes_by_relation[[query[:schema], query[:relation]]] %>
|
73
|
-
<% if @growth_bytes_by_relation[[query[:schema], query[:relation]]] < 0 %>-<% end %><%= PgHero.pretty_size(@growth_bytes_by_relation[[query[:schema], query[:relation]]].abs) %>
|
74
|
-
<% else %>
|
75
|
-
<span class="text-muted">Unknown</span>
|
76
|
-
<% end %>
|
77
|
-
</td>
|
49
|
+
<th style="width: 15%;"><%= link_to "#{@days}d Growth", @header_options.merge(sort: "growth") %></th>
|
78
50
|
<% end %>
|
79
51
|
</tr>
|
80
|
-
|
81
|
-
|
82
|
-
|
52
|
+
</thead>
|
53
|
+
<tbody>
|
54
|
+
<% @relation_sizes.each do |query| %>
|
55
|
+
<tr>
|
56
|
+
<td style="<%= query[:type] == "index" ? "font-style: italic;" : "" %>">
|
57
|
+
<span style="word-break: break-all;">
|
58
|
+
<% name = query[:relation] || query[:table] %>
|
59
|
+
<% if @space_stats_enabled %>
|
60
|
+
<%= link_to name, relation_space_path(name, schema: query[:schema]), target: "_blank", style: "color: inherit;" %>
|
61
|
+
<% else %>
|
62
|
+
<%= name %>
|
63
|
+
<% end %>
|
64
|
+
</span>
|
65
|
+
<% if query[:schema] != "public" %>
|
66
|
+
<span class="text-muted"><%= query[:schema] %></span>
|
67
|
+
<% end %>
|
68
|
+
<% if @unused_index_names.include?(query[:relation]) %>
|
69
|
+
<span class="unused-index">UNUSED</span>
|
70
|
+
<% end %>
|
71
|
+
</td>
|
72
|
+
<td><%= query[:size] %></td>
|
73
|
+
<% if @space_stats_enabled %>
|
74
|
+
<td>
|
75
|
+
<% if @growth_bytes_by_relation[[query[:schema], query[:relation]]] %>
|
76
|
+
<% if @growth_bytes_by_relation[[query[:schema], query[:relation]]] < 0 %>-<% end %><%= PgHero.pretty_size(@growth_bytes_by_relation[[query[:schema], query[:relation]]].abs) %>
|
77
|
+
<% else %>
|
78
|
+
<span class="text-muted">Unknown</span>
|
79
|
+
<% end %>
|
80
|
+
</td>
|
81
|
+
<% end %>
|
82
|
+
</tr>
|
83
|
+
<% end %>
|
84
|
+
</tbody>
|
85
|
+
</table>
|
86
|
+
<% end %>
|
83
87
|
</div>
|