pghero 2.8.3 → 3.3.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 +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>
|