pghero 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/app/assets/javascripts/pghero/application.js +14 -0
- data/app/assets/stylesheets/pghero/application.css +101 -1
- data/app/controllers/pg_hero/home_controller.rb +3 -1
- data/app/helpers/pg_hero/home_helper.rb +1 -1
- data/app/views/layouts/pg_hero/application.html.erb +2 -2
- 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 +2 -2
- data/app/views/pg_hero/home/_suggested_index.html.erb +6 -5
- data/app/views/pg_hero/home/connections.html.erb +6 -6
- data/app/views/pg_hero/home/explain.html.erb +2 -2
- data/app/views/pg_hero/home/index.html.erb +19 -19
- 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 +4 -4
- data/app/views/pg_hero/home/relation_space.html.erb +3 -3
- data/app/views/pg_hero/home/show_query.html.erb +19 -19
- data/app/views/pg_hero/home/space.html.erb +11 -11
- data/app/views/pg_hero/home/system.html.erb +12 -12
- data/app/views/pg_hero/home/tune.html.erb +2 -2
- data/lib/pghero/database.rb +2 -2
- data/lib/pghero/methods/basic.rb +3 -3
- data/lib/pghero/methods/explain.rb +1 -1
- data/lib/pghero/methods/query_stats.rb +1 -1
- data/lib/pghero/methods/suggested_indexes.rb +1 -1
- data/lib/pghero/methods/system.rb +2 -2
- data/lib/pghero/version.rb +1 -1
- data/lib/pghero.rb +2 -4
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 725af86da804f9f7869d727483cdc077de020c77cf5d364abecb943db6b2e7e9
|
4
|
+
data.tar.gz: 0e15c553a23d7288711f1b16e619ffff6cc38bbb26a1539477ed6f7eb11e2e6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 435b20217db356ac5d28450d92a5f96d44fef1ea3a2b909f8d9c713393f3dee3f9fea9e2be6737320280d1663dbab20fb19165d01b70d4842ea6f32978bb0465
|
7
|
+
data.tar.gz: edab3df03d6a47681f9fff134d0fc5fa070fa5262d74092948aa76ade0dbba2977ef885120894ec0cb4a1e007b46454373c529239cb4fc1f002b2d8702dba17e
|
data/CHANGELOG.md
CHANGED
@@ -157,3 +157,17 @@ function initSlider() {
|
|
157
157
|
refreshStats(false);
|
158
158
|
});
|
159
159
|
}
|
160
|
+
|
161
|
+
$(document).on("click", ".query-code", function () {
|
162
|
+
this.style.maxHeight = "none";
|
163
|
+
});
|
164
|
+
|
165
|
+
$(document).on("click", ".migration-link", function (e) {
|
166
|
+
e.preventDefault();
|
167
|
+
$(this).parent().next(".migration").css("display", "block");
|
168
|
+
});
|
169
|
+
|
170
|
+
$(document).on("click", ".show-details", function () {
|
171
|
+
$(this).nextAll(".details").css("display", "block");
|
172
|
+
$(this).css("display", "none");
|
173
|
+
});
|
@@ -483,12 +483,12 @@ body {
|
|
483
483
|
line-height: 300px;
|
484
484
|
text-align: center;
|
485
485
|
color: #999;
|
486
|
+
margin-bottom: 20px;
|
486
487
|
}
|
487
488
|
|
488
489
|
.pie-chart {
|
489
490
|
height: 260px;
|
490
491
|
line-height: 260px;
|
491
|
-
margin-bottom: 20px;
|
492
492
|
}
|
493
493
|
|
494
494
|
.unused-index {
|
@@ -503,8 +503,18 @@ body {
|
|
503
503
|
font-weight: bold;
|
504
504
|
}
|
505
505
|
|
506
|
+
.origins-table {
|
507
|
+
table-layout: auto;
|
508
|
+
}
|
509
|
+
|
506
510
|
.origin {
|
507
511
|
word-break: break-word;
|
512
|
+
width: 90%;
|
513
|
+
}
|
514
|
+
|
515
|
+
.origin-pct {
|
516
|
+
text-align: right;
|
517
|
+
width: 10%;
|
508
518
|
}
|
509
519
|
|
510
520
|
.duplicate-indexes td {
|
@@ -518,3 +528,93 @@ body {
|
|
518
528
|
.no-outline:focus {
|
519
529
|
outline: none;
|
520
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
|
+
}
|
@@ -166,6 +166,8 @@ module PgHero
|
|
166
166
|
|
167
167
|
if !@historical_query_stats_enabled || request.xhr?
|
168
168
|
set_suggested_indexes
|
169
|
+
else
|
170
|
+
@debug = params[:debug].present?
|
169
171
|
end
|
170
172
|
|
171
173
|
# fix back button issue with caching
|
@@ -274,7 +276,7 @@ module PgHero
|
|
274
276
|
|
275
277
|
def free_space_stats
|
276
278
|
render json: [
|
277
|
-
{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}
|
278
280
|
]
|
279
281
|
end
|
280
282
|
|
@@ -7,10 +7,10 @@
|
|
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.min", "pghero/application" %>
|
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,7 +4,7 @@
|
|
4
4
|
<div id="range-start"></div>
|
5
5
|
</div>
|
6
6
|
|
7
|
-
|
7
|
+
<%= javascript_tag nonce: true do %>
|
8
8
|
var sort = <%= pghero_js_value(@sort) %>;
|
9
9
|
var minAverageTime = <%= pghero_js_value(@min_average_time) %>;
|
10
10
|
var minCalls = <%= pghero_js_value(@min_calls) %>;
|
@@ -13,4 +13,4 @@
|
|
13
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] %>
|
@@ -7,24 +7,24 @@
|
|
7
7
|
<h3>By Database</h3>
|
8
8
|
|
9
9
|
<div id="chart-1" class="chart pie-chart">Loading...</div>
|
10
|
-
|
10
|
+
<%= javascript_tag nonce: true do %>
|
11
11
|
new Chartkick.PieChart("chart-1", <%= pghero_js_value(@connections_by_database) %>);
|
12
|
-
|
12
|
+
<% end %>
|
13
13
|
|
14
14
|
<h3>By User</h3>
|
15
15
|
|
16
16
|
<div id="chart-2" class="chart pie-chart">Loading...</div>
|
17
|
-
|
17
|
+
<%= javascript_tag nonce: true do %>
|
18
18
|
new Chartkick.PieChart("chart-2", <%= pghero_js_value(@connections_by_user) %>);
|
19
|
-
|
19
|
+
<% end %>
|
20
20
|
|
21
21
|
<% if @connections_by_ssl_status %>
|
22
22
|
<h3>By Security</h3>
|
23
23
|
|
24
24
|
<div id="chart-3" class="chart pie-chart">Loading...</div>
|
25
|
-
|
25
|
+
<%= javascript_tag nonce: true do %>
|
26
26
|
new Chartkick.PieChart("chart-3", <%= pghero_js_value(@connections_by_ssl_status) %>);
|
27
|
-
|
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>
|
@@ -127,7 +127,7 @@
|
|
127
127
|
<table class="table">
|
128
128
|
<thead>
|
129
129
|
<tr>
|
130
|
-
<th
|
130
|
+
<th class="width-33">Column</th>
|
131
131
|
<th>Sequence</th>
|
132
132
|
</tr>
|
133
133
|
</thead>
|
@@ -251,7 +251,7 @@
|
|
251
251
|
<thead>
|
252
252
|
<tr>
|
253
253
|
<th>Table</th>
|
254
|
-
<th
|
254
|
+
<th class="width-20">Transactions Left</th>
|
255
255
|
</tr>
|
256
256
|
</thead>
|
257
257
|
<tbody>
|
@@ -279,9 +279,9 @@
|
|
279
279
|
<thead>
|
280
280
|
<tr>
|
281
281
|
<th>Column</th>
|
282
|
-
<th
|
283
|
-
<th
|
284
|
-
<th
|
282
|
+
<th class="width-20">Type</th>
|
283
|
+
<th class="width-20">Values Left</th>
|
284
|
+
<th class="width-10">% Left</th>
|
285
285
|
</tr>
|
286
286
|
</thead>
|
287
287
|
<tbody>
|
@@ -332,7 +332,7 @@
|
|
332
332
|
</td>
|
333
333
|
</tr>
|
334
334
|
<tr>
|
335
|
-
<td
|
335
|
+
<td class="query-row">
|
336
336
|
<pre><code>DROP INDEX CONCURRENTLY <%= pghero_pretty_ident(index[:name], schema: index[:schema]) %>;
|
337
337
|
<%= index[:definition].sub("CREATE INDEX ", "CREATE INDEX CONCURRENTLY ") %>;</code></pre>
|
338
338
|
</td>
|
@@ -366,7 +366,7 @@
|
|
366
366
|
</td>
|
367
367
|
</tr>
|
368
368
|
<tr>
|
369
|
-
<td
|
369
|
+
<td class="query-row">
|
370
370
|
<pre><code>ALTER TABLE <%= pghero_pretty_ident(constraint[:table], schema: constraint[:schema]) %> VALIDATE CONSTRAINT <%= pghero_pretty_ident(constraint[:name]) %>;</code></pre>
|
371
371
|
</td>
|
372
372
|
</tr>
|
@@ -383,15 +383,15 @@
|
|
383
383
|
<p>
|
384
384
|
These indexes exist, but aren’t needed. Remove them
|
385
385
|
<% if @show_migrations %>
|
386
|
-
<a href="
|
386
|
+
<a href="#" class="migration-link">with a migration</a>
|
387
387
|
<% end %>
|
388
388
|
for faster writes.
|
389
389
|
</p>
|
390
390
|
|
391
|
-
<div
|
391
|
+
<div class="migration">
|
392
392
|
<pre>rails generate migration remove_unneeded_indexes</pre>
|
393
393
|
<p>And paste</p>
|
394
|
-
<pre
|
394
|
+
<pre><% @duplicate_indexes.each do |query| %>
|
395
395
|
<%= pghero_remove_index(query[:unneeded_index]) %><% end %></pre>
|
396
396
|
</div>
|
397
397
|
|
@@ -426,14 +426,14 @@
|
|
426
426
|
Add indexes to speed up queries.
|
427
427
|
<% if @show_migrations %>
|
428
428
|
Here’s a
|
429
|
-
<a href="
|
429
|
+
<a href="#" class="migration-link">migration</a> to help.
|
430
430
|
<% end %>
|
431
431
|
</p>
|
432
432
|
|
433
|
-
<div
|
433
|
+
<div class="migration">
|
434
434
|
<pre>rails generate migration add_suggested_indexes</pre>
|
435
435
|
<p>And paste</p>
|
436
|
-
<pre
|
436
|
+
<pre>commit_db_transaction
|
437
437
|
<% @suggested_indexes.each do |index| %>
|
438
438
|
<% if index[:using] && index[:using] != "btree" %>
|
439
439
|
add_index <%= index[:table].to_sym.inspect %>, <%= index[:columns].first.inspect %>, using: <%= index[:using].inspect %>, algorithm: :concurrently
|
@@ -487,15 +487,15 @@ pg_stat_statements.track = all</pre>
|
|
487
487
|
<p>
|
488
488
|
Unused indexes cause unnecessary overhead. Remove them
|
489
489
|
<% if @show_migrations %>
|
490
|
-
<a href="
|
490
|
+
<a href="#" class="migration-link">with a migration</a>
|
491
491
|
<% end %>
|
492
492
|
for faster writes.
|
493
493
|
</p>
|
494
494
|
|
495
|
-
<div
|
495
|
+
<div class="migration">
|
496
496
|
<pre>rails generate migration remove_unused_indexes</pre>
|
497
497
|
<p>And paste</p>
|
498
|
-
<pre
|
498
|
+
<pre><% @unused_indexes.each do |query| %>
|
499
499
|
<%= pghero_remove_index(query)%><% end %></pre>
|
500
500
|
</div>
|
501
501
|
|
@@ -503,7 +503,7 @@ pg_stat_statements.track = all</pre>
|
|
503
503
|
<thead>
|
504
504
|
<tr>
|
505
505
|
<th>Name</th>
|
506
|
-
<th
|
506
|
+
<th class="width-20">Index Size</th>
|
507
507
|
</tr>
|
508
508
|
</thead>
|
509
509
|
<tbody>
|
@@ -518,6 +518,6 @@ pg_stat_statements.track = all</pre>
|
|
518
518
|
</div>
|
519
519
|
<% end %>
|
520
520
|
|
521
|
-
|
521
|
+
<%= javascript_tag nonce: true do %>
|
522
522
|
highlightQueries();
|
523
|
-
|
523
|
+
<% end %>
|
@@ -25,15 +25,15 @@ ALTER INDEX new_index RENAME TO index;</code></pre>
|
|
25
25
|
<thead>
|
26
26
|
<tr>
|
27
27
|
<th>Index</th>
|
28
|
-
<th
|
29
|
-
<th
|
28
|
+
<th class="width-15">Bloat</th>
|
29
|
+
<th class="width-15">Size</th>
|
30
30
|
</tr>
|
31
31
|
</thead>
|
32
32
|
<tbody>
|
33
33
|
<% @index_bloat.each do |index| %>
|
34
34
|
<tr>
|
35
35
|
<td>
|
36
|
-
<span
|
36
|
+
<span class="break-all"><%= index[:index] %></span>
|
37
37
|
<% if index[:primary] %>
|
38
38
|
<span class="primary-key">PRIMARY</span>
|
39
39
|
<% end %>
|
@@ -43,7 +43,7 @@ ALTER INDEX new_index RENAME TO index;</code></pre>
|
|
43
43
|
</tr>
|
44
44
|
<% if @show_sql && !index[:primary] %>
|
45
45
|
<tr>
|
46
|
-
<td colspan="3"
|
46
|
+
<td colspan="3" class="query-row">
|
47
47
|
<% new_index = "new_#{index[:index]}".first(63) %>
|
48
48
|
<pre><code><%= index[:definition].sub(" INDEX ", " INDEX CONCURRENTLY \n ").sub(index[:index], new_index) %>;
|
49
49
|
|
@@ -67,6 +67,6 @@ ALTER INDEX <%= pghero_pretty_ident(new_index) %>
|
|
67
67
|
<% end %>
|
68
68
|
</div>
|
69
69
|
|
70
|
-
|
70
|
+
<%= javascript_tag nonce: true do %>
|
71
71
|
highlightQueries();
|
72
|
-
|
72
|
+
<% end %>
|
@@ -5,10 +5,10 @@
|
|
5
5
|
<thead>
|
6
6
|
<tr>
|
7
7
|
<th>Table</th>
|
8
|
-
<th
|
9
|
-
<th
|
8
|
+
<th class="width-20">Last Vacuum</th>
|
9
|
+
<th class="width-20">Last Analyze</th>
|
10
10
|
<% if @show_dead_rows %>
|
11
|
-
<th
|
11
|
+
<th class="width-20">Dead Rows</th>
|
12
12
|
<% end %>
|
13
13
|
</tr>
|
14
14
|
</thead>
|
@@ -4,13 +4,13 @@
|
|
4
4
|
<% end %>
|
5
5
|
|
6
6
|
<% if !@historical_query_stats_enabled %>
|
7
|
-
<h1
|
7
|
+
<h1 class="push-left">Queries</h1>
|
8
8
|
<% end %>
|
9
9
|
|
10
10
|
<% if @historical_query_stats_enabled %>
|
11
11
|
<%= render partial: "query_stats_slider" %>
|
12
12
|
<% elsif @database.query_stats_table_exists? && (columns = @database.missing_query_stats_columns).any? %>
|
13
|
-
<div
|
13
|
+
<div class="clear-both">
|
14
14
|
<p>Add missing columns to re-enable historical query stats.</p>
|
15
15
|
<pre><code><% @database.missing_query_stats_columns.each do |column| %>ALTER TABLE pghero_query_stats ADD COLUMN "<%= column %>" <%= column == "query_hash" ? "bigint" : "text" %>;
|
16
16
|
<% end %></code></pre>
|
@@ -23,9 +23,9 @@
|
|
23
23
|
<div class="alert alert-danger">Cannot understand start or end time.</div>
|
24
24
|
<% elsif @query_stats.any? || @historical_query_stats_enabled %>
|
25
25
|
<%= render partial: "queries_table", locals: {queries: @query_stats, sort_headers: true} %>
|
26
|
-
|
26
|
+
<%= javascript_tag nonce: true do %>
|
27
27
|
highlightQueries();
|
28
|
-
|
28
|
+
<% end %>
|
29
29
|
<% else %>
|
30
30
|
<p>Stats are not available yet. Come back soon!</p>
|
31
31
|
<% end %>
|
@@ -7,8 +7,8 @@
|
|
7
7
|
</h1>
|
8
8
|
|
9
9
|
<h1>Size</h1>
|
10
|
-
<div id="chart-1" class="chart"
|
11
|
-
|
10
|
+
<div id="chart-1" class="chart">Loading...</div>
|
11
|
+
<%= javascript_tag nonce: true do %>
|
12
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
|
+
<% end %>
|
14
14
|
</div>
|
@@ -1,8 +1,8 @@
|
|
1
1
|
<div class="content">
|
2
|
-
<pre><code
|
3
|
-
|
2
|
+
<pre><code class="query-code"><%= @query %></code></pre>
|
3
|
+
<%= javascript_tag nonce: true do %>
|
4
4
|
highlightQueries()
|
5
|
-
|
5
|
+
<% end %>
|
6
6
|
|
7
7
|
<% if @explain_enabled && @explainable_query %>
|
8
8
|
<p>
|
@@ -11,11 +11,11 @@
|
|
11
11
|
<% end %>
|
12
12
|
|
13
13
|
<% if @origins && @origins.keys.select { |k| k.length > 0 }.any? %>
|
14
|
-
<table
|
14
|
+
<table class="origins-table">
|
15
15
|
<thead>
|
16
16
|
<tr>
|
17
17
|
<th colspan="2">
|
18
|
-
<div
|
18
|
+
<div class="push-right">Approx. Time</div>
|
19
19
|
Origin
|
20
20
|
</th>
|
21
21
|
</tr>
|
@@ -23,14 +23,14 @@
|
|
23
23
|
<tbody>
|
24
24
|
<% @origins.sort_by { |o, c| [-c, o.to_s] }.each do |origin, count| %>
|
25
25
|
<tr>
|
26
|
-
<td class="origin"
|
26
|
+
<td class="origin">
|
27
27
|
<% if origin.length > 0 %>
|
28
28
|
<%= origin %>
|
29
29
|
<% else %>
|
30
30
|
<span class="text-muted">Unknown</span>
|
31
31
|
<% end %>
|
32
32
|
</td>
|
33
|
-
<td
|
33
|
+
<td class="origin-pct">
|
34
34
|
<% pct = (100.0 * count / @total_count).round %>
|
35
35
|
<% if pct == 0 %>
|
36
36
|
< 1%
|
@@ -47,22 +47,22 @@
|
|
47
47
|
<!-- chart -->
|
48
48
|
<% if @chart_data %>
|
49
49
|
<h1>Total Time <small>ms</small></h1>
|
50
|
-
<div id="chart-1" class="chart"
|
51
|
-
|
50
|
+
<div id="chart-1" class="chart">Loading...</div>
|
51
|
+
<%= javascript_tag nonce: true do %>
|
52
52
|
new Chartkick.LineChart("chart-1", <%= pghero_js_value(@chart_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
53
|
-
|
53
|
+
<% end %>
|
54
54
|
|
55
55
|
<h1>Average Time <small>ms</small></h1>
|
56
|
-
<div id="chart-2" class="chart"
|
57
|
-
|
56
|
+
<div id="chart-2" class="chart">Loading...</div>
|
57
|
+
<%= javascript_tag nonce: true do %>
|
58
58
|
new Chartkick.LineChart("chart-2", <%= pghero_js_value(@chart2_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
59
|
-
|
59
|
+
<% end %>
|
60
60
|
|
61
61
|
<h1>Calls</h1>
|
62
|
-
<div id="chart-3" class="chart"
|
63
|
-
|
62
|
+
<div id="chart-3" class="chart">Loading...</div>
|
63
|
+
<%= javascript_tag nonce: true do %>
|
64
64
|
new Chartkick.LineChart("chart-3", <%= pghero_js_value(@chart3_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
65
|
-
|
65
|
+
<% end %>
|
66
66
|
<% else %>
|
67
67
|
<p>
|
68
68
|
Enable
|
@@ -77,8 +77,8 @@
|
|
77
77
|
<table>
|
78
78
|
<thead>
|
79
79
|
<tr>
|
80
|
-
<th
|
81
|
-
<th
|
80
|
+
<th class="width-25">Name</th>
|
81
|
+
<th class="width-25">Rows</th>
|
82
82
|
<th>Indexes</th>
|
83
83
|
</tr>
|
84
84
|
</thead>
|
@@ -86,7 +86,7 @@
|
|
86
86
|
<% @tables.each do |table| %>
|
87
87
|
<tr>
|
88
88
|
<td><%= table %></td>
|
89
|
-
<td><%= @row_counts[table] %></td>
|
89
|
+
<td><%= number_with_delimiter(@row_counts[table]) if @row_counts[table] %></td>
|
90
90
|
<td>
|
91
91
|
<% if @indexes_timeout %>
|
92
92
|
Not available
|
@@ -4,10 +4,10 @@
|
|
4
4
|
<p>Database Size: <%= @database_size %></p>
|
5
5
|
|
6
6
|
<% if @system_stats_enabled %>
|
7
|
-
<div id="chart-1" class="chart"
|
8
|
-
|
7
|
+
<div id="chart-1" class="chart">Loading...</div>
|
8
|
+
<%= javascript_tag nonce: true do %>
|
9
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
|
+
<% end %>
|
11
11
|
<% end %>
|
12
12
|
|
13
13
|
<!--
|
@@ -20,7 +20,7 @@
|
|
20
20
|
<p>
|
21
21
|
<%= pluralize(@unused_indexes.size, "unused index") %>. Remove them
|
22
22
|
<% if @show_migrations %>
|
23
|
-
<a href="
|
23
|
+
<a href="#" class="migration-link">with a migration</a>
|
24
24
|
<% end %>
|
25
25
|
for faster writes.
|
26
26
|
|
@@ -29,10 +29,10 @@
|
|
29
29
|
<% end %>
|
30
30
|
</p>
|
31
31
|
|
32
|
-
<div
|
32
|
+
<div class="migration">
|
33
33
|
<pre>rails generate migration remove_unused_indexes</pre>
|
34
34
|
<p>And paste</p>
|
35
|
-
<pre
|
35
|
+
<pre><% @unused_indexes.sort_by { |q| [-q[:size_bytes], q[:index]] }.each do |query| %>
|
36
36
|
<%= pghero_remove_index(query) %><% end %></pre>
|
37
37
|
</div>
|
38
38
|
<% end %>
|
@@ -44,20 +44,20 @@
|
|
44
44
|
<thead>
|
45
45
|
<tr>
|
46
46
|
<th><%= link_to (@only_tables ? "Table" : "Relation"), @header_options.merge(sort: "name") %></th>
|
47
|
-
<th
|
47
|
+
<th class="width-15"><%= link_to "Size", @header_options %></th>
|
48
48
|
<% if @space_stats_enabled %>
|
49
|
-
<th
|
49
|
+
<th class="width-15"><%= link_to "#{@days}d Growth", @header_options.merge(sort: "growth") %></th>
|
50
50
|
<% end %>
|
51
51
|
</tr>
|
52
52
|
</thead>
|
53
53
|
<tbody>
|
54
54
|
<% @relation_sizes.each do |query| %>
|
55
55
|
<tr>
|
56
|
-
<td
|
57
|
-
<span
|
56
|
+
<td class="<%= query[:type] == "index" ? "space-index" : "" %>">
|
57
|
+
<span class="break-all">
|
58
58
|
<% name = query[:relation] || query[:table] %>
|
59
59
|
<% if @space_stats_enabled %>
|
60
|
-
<%= link_to name, relation_space_path(name, schema: query[:schema]), target: "_blank",
|
60
|
+
<%= link_to name, relation_space_path(name, schema: query[:schema]), target: "_blank", class: "relation-link" %>
|
61
61
|
<% else %>
|
62
62
|
<%= name %>
|
63
63
|
<% end %>
|
@@ -7,28 +7,28 @@
|
|
7
7
|
<% path_options = {params: {duration: @duration, period: @period}} %>
|
8
8
|
|
9
9
|
<h1>CPU</h1>
|
10
|
-
<div id="chart-1" class="chart"
|
11
|
-
|
10
|
+
<div id="chart-1" class="chart">Loading...</div>
|
11
|
+
<%= javascript_tag nonce: true do %>
|
12
12
|
new Chartkick.LineChart("chart-1", <%= pghero_js_value(cpu_usage_path(path_options)) %>, {max: 100, colors: ["#5bc0de"], suffix: "%", library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
13
|
-
|
13
|
+
<% end %>
|
14
14
|
|
15
15
|
<h1>Load</h1>
|
16
|
-
<div id="chart-2" class="chart"
|
17
|
-
|
16
|
+
<div id="chart-2" class="chart">Loading...</div>
|
17
|
+
<%= javascript_tag nonce: true do %>
|
18
18
|
new Chartkick.LineChart("chart-2", <%= pghero_js_value(load_stats_path(path_options)) %>, {colors: ["#5bc0de", "#d9534f"], library: {plugins: {tooltip: {intersect: false, mode: "nearest"}}}})
|
19
|
-
|
19
|
+
<% end %>
|
20
20
|
|
21
21
|
<h1>Connections</h1>
|
22
|
-
<div id="chart-3" class="chart"
|
23
|
-
|
22
|
+
<div id="chart-3" class="chart">Loading...</div>
|
23
|
+
<%= javascript_tag nonce: true do %>
|
24
24
|
new Chartkick.LineChart("chart-3", <%= pghero_js_value(connection_stats_path(path_options)) %>, {colors: ["#5bc0de"], library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
25
|
-
|
25
|
+
<% end %>
|
26
26
|
|
27
27
|
<% if @database.replica? %>
|
28
28
|
<h1>Replication Lag</h1>
|
29
|
-
<div id="chart-4" class="chart"
|
30
|
-
|
29
|
+
<div id="chart-4" class="chart">Loading...</div>
|
30
|
+
<%= javascript_tag nonce: true do %>
|
31
31
|
new Chartkick.LineChart("chart-4", <%= pghero_js_value(replication_lag_stats_path(path_options)) %>, {colors: ["#5bc0de"], library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
32
|
-
|
32
|
+
<% end %>
|
33
33
|
<% end %>
|
34
34
|
</div>
|
@@ -5,7 +5,7 @@
|
|
5
5
|
<thead>
|
6
6
|
<tr>
|
7
7
|
<th>Setting</th>
|
8
|
-
<th
|
8
|
+
<th class="width-20">Value</th>
|
9
9
|
</tr>
|
10
10
|
</thead>
|
11
11
|
<tbody>
|
@@ -30,7 +30,7 @@
|
|
30
30
|
<thead>
|
31
31
|
<tr>
|
32
32
|
<th>Setting</th>
|
33
|
-
<th
|
33
|
+
<th class="width-20">Value</th>
|
34
34
|
</tr>
|
35
35
|
</thead>
|
36
36
|
<tbody>
|
data/lib/pghero/database.rb
CHANGED
@@ -126,7 +126,7 @@ module PgHero
|
|
126
126
|
# rough check for Postgres adapter
|
127
127
|
# keep this message generic so it's useful
|
128
128
|
# when empty url set in Docker image pghero.yml
|
129
|
-
unless @connection_model.connection.adapter_name
|
129
|
+
unless @connection_model.connection.adapter_name.match?(/postg/i)
|
130
130
|
raise Error, "Invalid connection URL"
|
131
131
|
end
|
132
132
|
@adapter_checked = true
|
@@ -145,7 +145,7 @@ module PgHero
|
|
145
145
|
config_options = {env_name: PgHero.env, PgHero.spec_name_key => config["spec"], PgHero.include_replicas_key => true}
|
146
146
|
resolved = ActiveRecord::Base.configurations.configs_for(**config_options)
|
147
147
|
raise Error, "Spec not found: #{config["spec"]}" unless resolved
|
148
|
-
url =
|
148
|
+
url = resolved.configuration_hash
|
149
149
|
end
|
150
150
|
|
151
151
|
url = url.dup
|
data/lib/pghero/methods/basic.rb
CHANGED
@@ -42,10 +42,10 @@ module PgHero
|
|
42
42
|
retries = 0
|
43
43
|
begin
|
44
44
|
result = conn.select_all(add_source(squish(sql)))
|
45
|
-
if ActiveRecord::VERSION::
|
46
|
-
result = result.map(&:symbolize_keys)
|
45
|
+
if ActiveRecord::VERSION::MAJOR >= 8
|
46
|
+
result = result.to_a.map(&:symbolize_keys)
|
47
47
|
else
|
48
|
-
result = result.map
|
48
|
+
result = result.map(&:symbolize_keys)
|
49
49
|
end
|
50
50
|
if filter_data
|
51
51
|
query_columns.each do |column|
|
@@ -9,7 +9,7 @@ module PgHero
|
|
9
9
|
|
10
10
|
# use transaction for safety
|
11
11
|
with_transaction(statement_timeout: (explain_timeout_sec * 1000).round, rollback: true) do
|
12
|
-
if (sql.
|
12
|
+
if (sql.delete_suffix(";").include?(";") || sql.upcase.include?("COMMIT")) && !explain_safe?
|
13
13
|
raise ActiveRecord::StatementInvalid, "Unsafe statement"
|
14
14
|
end
|
15
15
|
explanation = execute("EXPLAIN #{sql}").map { |v| v["QUERY PLAN"] }.join("\n")
|
@@ -322,7 +322,7 @@ module PgHero
|
|
322
322
|
all_queries_total_minutes: stats2.sum { |s| s[:all_queries_total_minutes] }
|
323
323
|
}
|
324
324
|
value[:total_percent] = value[:total_minutes] * 100.0 / value[:all_queries_total_minutes]
|
325
|
-
value[:explainable_query] = stats2.map { |s| s[:explainable_query] }.
|
325
|
+
value[:explainable_query] = stats2.map { |s| s[:explainable_query] }.find { |q| q && explainable?(q) }
|
326
326
|
query_stats << value
|
327
327
|
end
|
328
328
|
query_stats
|
@@ -46,7 +46,7 @@ module PgHero
|
|
46
46
|
indexes += existing_columns["brin"][index[:table]]
|
47
47
|
end
|
48
48
|
|
49
|
-
covering_index = indexes.find { |e| index_covers?(e.map { |v| v.
|
49
|
+
covering_index = indexes.find { |e| index_covers?(e.map { |v| v.delete_suffix(" inet_ops") }, index[:columns]) }
|
50
50
|
if covering_index
|
51
51
|
best_index[:covering_index] = covering_index
|
52
52
|
best_index[:explanation] = "Covered by index on (#{covering_index.join(", ")})"
|
@@ -136,8 +136,8 @@ module PgHero
|
|
136
136
|
start_time = end_time - duration
|
137
137
|
|
138
138
|
# validate input since we need to interpolate below
|
139
|
-
raise Error, "Invalid metric name" unless
|
140
|
-
raise Error, "Invalid database id" unless
|
139
|
+
raise Error, "Invalid metric name" unless /\A[a-z\/_]+\z/i.match?(metric_name)
|
140
|
+
raise Error, "Invalid database id" unless /\A[a-z0-9\-:]+\z/i.match?(gcp_database_id)
|
141
141
|
|
142
142
|
# we handle four situations:
|
143
143
|
# 1. google-cloud-monitoring-v3
|
data/lib/pghero/version.rb
CHANGED
data/lib/pghero.rb
CHANGED
@@ -243,14 +243,12 @@ module PgHero
|
|
243
243
|
|
244
244
|
# private
|
245
245
|
def connection_config(model)
|
246
|
-
|
246
|
+
model.connection_db_config.configuration_hash
|
247
247
|
end
|
248
248
|
|
249
249
|
# private
|
250
|
-
# Rails 6.1 deprecates `spec_name` for `name`
|
251
|
-
# https://github.com/rails/rails/pull/38536
|
252
250
|
def spec_name_key
|
253
|
-
|
251
|
+
:name
|
254
252
|
end
|
255
253
|
|
256
254
|
# private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pghero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '6'
|
19
|
+
version: '6.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '6'
|
26
|
+
version: '6.1'
|
27
27
|
description:
|
28
28
|
email: andrew@ankane.org
|
29
29
|
executables: []
|
@@ -117,14 +117,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
117
117
|
requirements:
|
118
118
|
- - ">="
|
119
119
|
- !ruby/object:Gem::Version
|
120
|
-
version: '
|
120
|
+
version: '3.1'
|
121
121
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
122
|
requirements:
|
123
123
|
- - ">="
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0'
|
126
126
|
requirements: []
|
127
|
-
rubygems_version: 3.5.
|
127
|
+
rubygems_version: 3.5.11
|
128
128
|
signing_key:
|
129
129
|
specification_version: 4
|
130
130
|
summary: A performance dashboard for Postgres
|