pghero 3.1.0 → 3.7.0
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 +78 -1
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/app/assets/javascripts/pghero/Chart.bundle.js +23379 -19766
- data/app/assets/javascripts/pghero/application.js +27 -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 +108 -2
- data/app/assets/stylesheets/pghero/nouislider.css +4 -10
- data/app/controllers/pg_hero/home_controller.rb +53 -16
- data/app/helpers/pg_hero/home_helper.rb +3 -3
- data/app/views/layouts/pg_hero/application.html.erb +3 -3
- data/app/views/pg_hero/home/_connections_table.html.erb +1 -1
- data/app/views/pg_hero/home/_live_queries_table.html.erb +8 -8
- data/app/views/pg_hero/home/_queries_table.html.erb +5 -5
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +8 -8
- data/app/views/pg_hero/home/_suggested_index.html.erb +6 -5
- data/app/views/pg_hero/home/connections.html.erb +12 -12
- data/app/views/pg_hero/home/explain.html.erb +2 -2
- data/app/views/pg_hero/home/index.html.erb +22 -20
- data/app/views/pg_hero/home/index_bloat.html.erb +6 -6
- data/app/views/pg_hero/home/maintenance.html.erb +3 -3
- data/app/views/pg_hero/home/queries.html.erb +7 -5
- data/app/views/pg_hero/home/relation_space.html.erb +4 -4
- data/app/views/pg_hero/home/show_query.html.erb +35 -31
- data/app/views/pg_hero/home/space.html.erb +50 -46
- data/app/views/pg_hero/home/system.html.erb +18 -18
- data/app/views/pg_hero/home/tune.html.erb +2 -2
- data/lib/generators/pghero/query_stats_generator.rb +1 -0
- data/lib/generators/pghero/space_stats_generator.rb +1 -0
- data/lib/pghero/database.rb +2 -2
- data/lib/pghero/engine.rb +4 -3
- data/lib/pghero/methods/basic.rb +26 -31
- data/lib/pghero/methods/connections.rb +4 -4
- data/lib/pghero/methods/constraints.rb +1 -1
- data/lib/pghero/methods/explain.rb +4 -3
- data/lib/pghero/methods/indexes.rb +8 -8
- data/lib/pghero/methods/kill.rb +1 -1
- data/lib/pghero/methods/maintenance.rb +3 -3
- data/lib/pghero/methods/queries.rb +2 -2
- data/lib/pghero/methods/query_stats.rb +34 -24
- data/lib/pghero/methods/replication.rb +2 -2
- data/lib/pghero/methods/sequences.rb +10 -5
- data/lib/pghero/methods/settings.rb +8 -1
- data/lib/pghero/methods/space.rb +20 -14
- data/lib/pghero/methods/suggested_indexes.rb +14 -7
- data/lib/pghero/methods/system.rb +12 -6
- data/lib/pghero/methods/tables.rb +4 -5
- data/lib/pghero/version.rb +1 -1
- data/lib/pghero.rb +35 -36
- 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 +8 -11
- 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 {
|
@@ -483,6 +483,12 @@ body {
|
|
483
483
|
line-height: 300px;
|
484
484
|
text-align: center;
|
485
485
|
color: #999;
|
486
|
+
margin-bottom: 20px;
|
487
|
+
}
|
488
|
+
|
489
|
+
.pie-chart {
|
490
|
+
height: 260px;
|
491
|
+
line-height: 260px;
|
486
492
|
}
|
487
493
|
|
488
494
|
.unused-index {
|
@@ -497,8 +503,18 @@ body {
|
|
497
503
|
font-weight: bold;
|
498
504
|
}
|
499
505
|
|
506
|
+
.origins-table {
|
507
|
+
table-layout: auto;
|
508
|
+
}
|
509
|
+
|
500
510
|
.origin {
|
501
511
|
word-break: break-word;
|
512
|
+
width: 90%;
|
513
|
+
}
|
514
|
+
|
515
|
+
.origin-pct {
|
516
|
+
text-align: right;
|
517
|
+
width: 10%;
|
502
518
|
}
|
503
519
|
|
504
520
|
.duplicate-indexes td {
|
@@ -512,3 +528,93 @@ body {
|
|
512
528
|
.no-outline:focus {
|
513
529
|
outline: none;
|
514
530
|
}
|
531
|
+
|
532
|
+
.width-10 {
|
533
|
+
width: 10%;
|
534
|
+
}
|
535
|
+
|
536
|
+
.width-15 {
|
537
|
+
width: 15%;
|
538
|
+
}
|
539
|
+
|
540
|
+
.width-20 {
|
541
|
+
width: 20%;
|
542
|
+
}
|
543
|
+
|
544
|
+
.width-25 {
|
545
|
+
width: 25%;
|
546
|
+
}
|
547
|
+
|
548
|
+
.width-33 {
|
549
|
+
width: 33.33%;
|
550
|
+
}
|
551
|
+
|
552
|
+
.right-5 {
|
553
|
+
margin-right: 5px;
|
554
|
+
}
|
555
|
+
|
556
|
+
.query-row {
|
557
|
+
border-top: none;
|
558
|
+
padding: 0;
|
559
|
+
}
|
560
|
+
|
561
|
+
.query-pre {
|
562
|
+
margin-top: 1em;
|
563
|
+
}
|
564
|
+
|
565
|
+
.query-code {
|
566
|
+
max-height: 230px;
|
567
|
+
overflow: hidden;
|
568
|
+
}
|
569
|
+
|
570
|
+
.break-all {
|
571
|
+
word-break: break-all;
|
572
|
+
}
|
573
|
+
|
574
|
+
.migration {
|
575
|
+
display: none;
|
576
|
+
}
|
577
|
+
|
578
|
+
.migration pre {
|
579
|
+
overflow: scroll;
|
580
|
+
white-space: pre;
|
581
|
+
word-break: normal;
|
582
|
+
}
|
583
|
+
|
584
|
+
.space-index {
|
585
|
+
font-style: italic;
|
586
|
+
}
|
587
|
+
|
588
|
+
a.relation-link {
|
589
|
+
color: inherit;
|
590
|
+
}
|
591
|
+
|
592
|
+
.push-left {
|
593
|
+
float: left;
|
594
|
+
}
|
595
|
+
|
596
|
+
.clear-both {
|
597
|
+
clear: both;
|
598
|
+
}
|
599
|
+
|
600
|
+
.create-index {
|
601
|
+
color: #eee;
|
602
|
+
background-color: #333;
|
603
|
+
}
|
604
|
+
|
605
|
+
.hide {
|
606
|
+
display: none;
|
607
|
+
}
|
608
|
+
|
609
|
+
.show-details {
|
610
|
+
float: right;
|
611
|
+
color: #f0ad4e;
|
612
|
+
margin-top: 0px;
|
613
|
+
padding: 10px;
|
614
|
+
cursor: pointer;
|
615
|
+
}
|
616
|
+
|
617
|
+
.details pre {
|
618
|
+
color: #f0ad4e;
|
619
|
+
background-color: #333;
|
620
|
+
}
|
@@ -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,7 +87,7 @@ 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)
|
@@ -157,8 +164,11 @@ module PgHero
|
|
157
164
|
)
|
158
165
|
end
|
159
166
|
|
160
|
-
|
161
|
-
|
167
|
+
if !@historical_query_stats_enabled || request.xhr?
|
168
|
+
set_suggested_indexes
|
169
|
+
else
|
170
|
+
@debug = params[:debug].present?
|
171
|
+
end
|
162
172
|
|
163
173
|
# fix back button issue with caching
|
164
174
|
response.headers["Cache-Control"] = "must-revalidate, no-store, no-cache, private"
|
@@ -178,7 +188,7 @@ module PgHero
|
|
178
188
|
@explainable_query = stats[:explainable_query]
|
179
189
|
|
180
190
|
if @show_details
|
181
|
-
query_hash_stats = @database.query_hash_stats(@query_hash, user: @user)
|
191
|
+
query_hash_stats = @database.query_hash_stats(@query_hash, user: @user, current: true)
|
182
192
|
|
183
193
|
@chart_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at].change(sec: 0), (r[:total_minutes] * 60 * 1000).round] }, library: chart_library_options}]
|
184
194
|
@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}]
|
@@ -193,7 +203,8 @@ module PgHero
|
|
193
203
|
|
194
204
|
if @tables.any?
|
195
205
|
@row_counts = @database.table_stats(table: @tables).to_h { |i| [i[:table], i[:estimated_rows]] }
|
196
|
-
@
|
206
|
+
indexes, @indexes_timeout = rescue_timeout([]) { @database.indexes }
|
207
|
+
@indexes_by_table = indexes.group_by { |i| i[:table] }
|
197
208
|
end
|
198
209
|
else
|
199
210
|
render_text "Unknown query", status: :not_found
|
@@ -239,9 +250,16 @@ module PgHero
|
|
239
250
|
stats =
|
240
251
|
case @database.system_stats_provider
|
241
252
|
when :azure
|
242
|
-
|
243
|
-
|
244
|
-
|
253
|
+
if @database.send(:azure_flexible_server?)
|
254
|
+
[
|
255
|
+
{name: "Read IOPS", data: @database.read_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options},
|
256
|
+
{name: "Write IOPS", data: @database.write_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options}
|
257
|
+
]
|
258
|
+
else
|
259
|
+
[
|
260
|
+
{name: "IO Consumption", data: @database.azure_stats("io_consumption_percent", **system_params), library: chart_library_options}
|
261
|
+
]
|
262
|
+
end
|
245
263
|
when :gcp
|
246
264
|
[
|
247
265
|
{name: "Read Ops", data: @database.read_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options},
|
@@ -258,7 +276,7 @@ module PgHero
|
|
258
276
|
|
259
277
|
def free_space_stats
|
260
278
|
render json: [
|
261
|
-
{name: "Free Space", data: @database.free_space_stats(duration: 14.days, period: 1.hour), library: chart_library_options}
|
279
|
+
{name: "Free Space", data: @database.free_space_stats(duration: 14.days, period: 1.hour), library: chart_library_options}
|
262
280
|
]
|
263
281
|
end
|
264
282
|
|
@@ -276,12 +294,14 @@ module PgHero
|
|
276
294
|
# need to prevent CSRF and DoS
|
277
295
|
if request.post? && @query.present?
|
278
296
|
begin
|
297
|
+
generic_plan = @database.server_version_num >= 160000 && @query.include?("$1")
|
298
|
+
|
279
299
|
explain_options =
|
280
300
|
case params[:commit]
|
281
301
|
when "Analyze"
|
282
302
|
{analyze: true}
|
283
303
|
when "Visualize"
|
284
|
-
if @explain_analyze_enabled
|
304
|
+
if @explain_analyze_enabled && !generic_plan
|
285
305
|
{analyze: true, costs: true, verbose: true, buffers: true, format: "json"}
|
286
306
|
else
|
287
307
|
{costs: true, verbose: true, format: "json"}
|
@@ -290,6 +310,8 @@ module PgHero
|
|
290
310
|
{}
|
291
311
|
end
|
292
312
|
|
313
|
+
explain_options[:generic_plan] = true if generic_plan
|
314
|
+
|
293
315
|
if explain_options[:analyze] && !@explain_analyze_enabled
|
294
316
|
render_text "Explain analyze not enabled", status: :bad_request
|
295
317
|
return
|
@@ -303,8 +325,10 @@ module PgHero
|
|
303
325
|
@error =
|
304
326
|
if message == "Unsafe statement"
|
305
327
|
"Unsafe statement"
|
306
|
-
elsif message.start_with?("PG::
|
328
|
+
elsif message.start_with?("PG::UndefinedParameter")
|
307
329
|
"Can't explain queries with bind parameters"
|
330
|
+
elsif message.include?("EXPLAIN options ANALYZE and GENERIC_PLAN cannot be used together")
|
331
|
+
"Can't analyze queries with bind parameters"
|
308
332
|
elsif message.start_with?("PG::SyntaxError")
|
309
333
|
"Syntax error with query"
|
310
334
|
elsif message.start_with?("PG::QueryCanceled")
|
@@ -395,6 +419,7 @@ module PgHero
|
|
395
419
|
redirect_backward alert: "The database user does not have permission to enable query stats"
|
396
420
|
end
|
397
421
|
|
422
|
+
# TODO disable if historical query stats enabled?
|
398
423
|
def reset_query_stats
|
399
424
|
success =
|
400
425
|
if @database.server_version_num >= 120000
|
@@ -440,14 +465,18 @@ module PgHero
|
|
440
465
|
end
|
441
466
|
|
442
467
|
def set_suggested_indexes(min_average_time = 0, min_calls = 0)
|
468
|
+
if @database.suggested_indexes_enabled? && !@indexes
|
469
|
+
@indexes, @indexes_timeout = rescue_timeout([]) { @database.indexes }
|
470
|
+
end
|
471
|
+
|
443
472
|
@suggested_indexes_by_query =
|
444
|
-
if @database.suggested_indexes_enabled?
|
445
|
-
@database.suggested_indexes_by_query(query_stats: @query_stats.select { |qs| qs[:average_time] >= min_average_time && qs[:calls] >= min_calls })
|
473
|
+
if !@indexes_timeout && @database.suggested_indexes_enabled?
|
474
|
+
@database.suggested_indexes_by_query(query_stats: @query_stats.select { |qs| qs[:average_time] >= min_average_time && qs[:calls] >= min_calls }, indexes: @indexes)
|
446
475
|
else
|
447
476
|
{}
|
448
477
|
end
|
449
478
|
|
450
|
-
@suggested_indexes = @database.suggested_indexes(suggested_indexes_by_query: @suggested_indexes_by_query
|
479
|
+
@suggested_indexes = @database.suggested_indexes(suggested_indexes_by_query: @suggested_indexes_by_query)
|
451
480
|
@query_stats_by_query = @query_stats.index_by { |q| q[:query] }
|
452
481
|
@debug = params[:debug].present?
|
453
482
|
end
|
@@ -495,5 +524,13 @@ module PgHero
|
|
495
524
|
redirect_to root_path, alert: "Query stats not enabled"
|
496
525
|
end
|
497
526
|
end
|
527
|
+
|
528
|
+
# rescue QueryCanceled for case when
|
529
|
+
# statement timeout is less than lock timeout
|
530
|
+
def rescue_timeout(default)
|
531
|
+
[yield, false]
|
532
|
+
rescue ActiveRecord::LockWaitTimeout, ActiveRecord::QueryCanceled
|
533
|
+
[default, true]
|
534
|
+
end
|
498
535
|
end
|
499
536
|
end
|
@@ -5,15 +5,15 @@ module PgHero
|
|
5
5
|
if schema && schema != "public"
|
6
6
|
ident = "#{schema}.#{table}"
|
7
7
|
end
|
8
|
-
if
|
8
|
+
if /\A[a-z0-9_]+\z/.match?(ident)
|
9
9
|
ident
|
10
10
|
else
|
11
11
|
@database.quote_ident(ident)
|
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)
|
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
<meta charset="utf-8" />
|
7
7
|
<%= favicon_link_tag "pghero/favicon.png" %>
|
8
|
-
<% if defined?(Propshaft::Railtie) %>
|
8
|
+
<% if defined?(Propshaft::Railtie) && Rails.application.assets.is_a?(Propshaft::Assembly) %>
|
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", nonce: true %>
|
11
11
|
<% else %>
|
12
12
|
<%= stylesheet_link_tag "pghero/application" %>
|
13
|
-
<%= javascript_include_tag "pghero/application" %>
|
13
|
+
<%= javascript_include_tag "pghero/application", nonce: true %>
|
14
14
|
<% end %>
|
15
15
|
</head>
|
16
16
|
<body>
|
@@ -1,10 +1,10 @@
|
|
1
1
|
<table class="table queries">
|
2
2
|
<thead>
|
3
3
|
<tr>
|
4
|
-
<th
|
5
|
-
<th
|
6
|
-
<th
|
7
|
-
<th
|
4
|
+
<th class="width-25">Pid</th>
|
5
|
+
<th class="width-25">Duration</th>
|
6
|
+
<th class="width-25">State</th>
|
7
|
+
<th class="width-25"></th>
|
8
8
|
</tr>
|
9
9
|
</thead>
|
10
10
|
<tbody>
|
@@ -39,15 +39,15 @@
|
|
39
39
|
</td>
|
40
40
|
</tr>
|
41
41
|
<tr>
|
42
|
-
<td colspan="
|
42
|
+
<td colspan="4" class="query-row">
|
43
43
|
<%= query[:source] %> <span class="text-muted"><%= query[:user] %></span>
|
44
|
-
<pre
|
44
|
+
<pre class="query-pre"><code><%= query[:query] %></code></pre>
|
45
45
|
</td>
|
46
46
|
</tr>
|
47
47
|
<% end %>
|
48
48
|
</tbody>
|
49
49
|
</table>
|
50
50
|
|
51
|
-
|
51
|
+
<%= javascript_tag nonce: true do %>
|
52
52
|
highlightQueries();
|
53
|
-
|
53
|
+
<% end %>
|
@@ -2,9 +2,9 @@
|
|
2
2
|
<% unless local_assigns[:xhr] %>
|
3
3
|
<thead>
|
4
4
|
<tr>
|
5
|
-
<th
|
6
|
-
<th
|
7
|
-
<th
|
5
|
+
<th class="width-33"><%= local_assigns[:sort_headers] ? link_to("Total Time", {sort: nil}, data: {sort: nil}) : "Total Time" %></th>
|
6
|
+
<th class="width-33"><%= local_assigns[:sort_headers] ? link_to("Average Time", {sort: "average_time"}, data: {sort: "average_time"}) : "Average Time" %></th>
|
7
|
+
<th class="width-33"><%= local_assigns[:sort_headers] ? link_to("Calls", {sort: "calls"}, data: {sort: "calls"}) : "Calls" %></th>
|
8
8
|
</tr>
|
9
9
|
</thead>
|
10
10
|
<% end %>
|
@@ -57,8 +57,8 @@
|
|
57
57
|
</td>
|
58
58
|
</tr>
|
59
59
|
<tr>
|
60
|
-
<td colspan="3"
|
61
|
-
<pre><code
|
60
|
+
<td colspan="3" class="query-row">
|
61
|
+
<pre><code class="query-code"><%= query[:query] %></code></pre>
|
62
62
|
<% if query[:query] == "<insufficient privilege>" %>
|
63
63
|
<p class="text-muted">For security reasons, only superusers can see queries executed by other users.</p>
|
64
64
|
<% end %>
|
@@ -4,13 +4,13 @@
|
|
4
4
|
<div id="range-start"></div>
|
5
5
|
</div>
|
6
6
|
|
7
|
-
|
8
|
-
<%=
|
9
|
-
<%=
|
10
|
-
<%=
|
11
|
-
<%=
|
12
|
-
<%=
|
13
|
-
<%=
|
7
|
+
<%= javascript_tag nonce: true do %>
|
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
|
+
<% end %>
|
@@ -1,13 +1,14 @@
|
|
1
1
|
<% if index && !details[:covering_index] %>
|
2
2
|
<% unless @debug %>
|
3
|
-
<div
|
3
|
+
<div class="show-details">Details</div>
|
4
4
|
<% end %>
|
5
|
-
<code><pre
|
5
|
+
<code><pre class="create-index">CREATE INDEX CONCURRENTLY ON <%= index[:table] %><% if index[:using] %> USING <%= index[:using] %><% end %> (<%= index[:columns].join(", ") %>)</pre></code>
|
6
6
|
<% end %>
|
7
|
-
<div
|
8
|
-
<code><pre
|
7
|
+
<div class="details<%= " hide" unless @debug %>">
|
8
|
+
<code><pre><% if details[:explanation] %><%= details[:explanation] %>
|
9
9
|
<% end %><% if details[:row_estimates] %>Rows: <%= details[:rows] %>
|
10
|
-
Row progression: <%= details[:row_progression].to_a.join(", ") %>
|
10
|
+
<% if details[:row_progression] %>Row progression: <%= details[:row_progression].to_a.join(", ") %>
|
11
|
+
<% end %>
|
11
12
|
|
12
13
|
Row estimates
|
13
14
|
<%= details[:row_estimates].to_a.map { |k, v| "- #{k}: #{v}" }.join("\n") %><% end %><% if details[:table_indexes] %>
|
@@ -6,25 +6,25 @@
|
|
6
6
|
<% if @total_connections > 0 %>
|
7
7
|
<h3>By Database</h3>
|
8
8
|
|
9
|
-
<div id="chart-1" class="chart
|
10
|
-
|
11
|
-
new Chartkick.PieChart("chart-1", <%=
|
12
|
-
|
9
|
+
<div id="chart-1" class="chart pie-chart">Loading...</div>
|
10
|
+
<%= javascript_tag nonce: true do %>
|
11
|
+
new Chartkick.PieChart("chart-1", <%= pghero_js_value(@connections_by_database) %>);
|
12
|
+
<% end %>
|
13
13
|
|
14
14
|
<h3>By User</h3>
|
15
15
|
|
16
|
-
<div id="chart-2" class="chart
|
17
|
-
|
18
|
-
new Chartkick.PieChart("chart-2", <%=
|
19
|
-
|
16
|
+
<div id="chart-2" class="chart pie-chart">Loading...</div>
|
17
|
+
<%= javascript_tag nonce: true do %>
|
18
|
+
new Chartkick.PieChart("chart-2", <%= pghero_js_value(@connections_by_user) %>);
|
19
|
+
<% end %>
|
20
20
|
|
21
21
|
<% if @connections_by_ssl_status %>
|
22
22
|
<h3>By Security</h3>
|
23
23
|
|
24
|
-
<div id="chart-3" class="chart
|
25
|
-
|
26
|
-
new Chartkick.PieChart("chart-3", <%=
|
27
|
-
|
24
|
+
<div id="chart-3" class="chart pie-chart">Loading...</div>
|
25
|
+
<%= javascript_tag nonce: true do %>
|
26
|
+
new Chartkick.PieChart("chart-3", <%= pghero_js_value(@connections_by_ssl_status) %>);
|
27
|
+
<% end %>
|
28
28
|
<% end %>
|
29
29
|
|
30
30
|
<%= render partial: "connections_table", locals: {connection_sources: @connection_sources} %>
|
@@ -4,9 +4,9 @@
|
|
4
4
|
<%= form_tag explain_path do %>
|
5
5
|
<div class="field"><%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %></div>
|
6
6
|
<p>
|
7
|
-
<%= submit_tag "Explain", class: "btn btn-info
|
7
|
+
<%= submit_tag "Explain", class: "btn btn-info right-5" %>
|
8
8
|
<% if @explain_analyze_enabled %>
|
9
|
-
<%= submit_tag "Analyze", class: "btn btn-danger
|
9
|
+
<%= submit_tag "Analyze", class: "btn btn-danger right-5" %>
|
10
10
|
<% end %>
|
11
11
|
<%= submit_tag "Visualize", class: "btn btn-danger" %>
|
12
12
|
</p>
|