pghero 3.1.0 → 3.3.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 +12 -0
- 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 +373 -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 +31 -9
- data/app/helpers/pg_hero/home_helper.rb +2 -2
- data/app/views/layouts/pg_hero/application.html.erb +1 -1
- 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/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 +16 -12
- 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/pghero/engine.rb +1 -1
- data/lib/pghero/methods/basic.rb +5 -8
- data/lib/pghero/methods/connections.rb +4 -4
- data/lib/pghero/methods/constraints.rb +1 -1
- 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 +19 -19
- data/lib/pghero/methods/replication.rb +2 -2
- data/lib/pghero/methods/sequences.rb +2 -2
- data/lib/pghero/methods/space.rb +18 -12
- data/lib/pghero/methods/suggested_indexes.rb +10 -6
- data/lib/pghero/methods/tables.rb +4 -5
- data/lib/pghero/version.rb +1 -1
- data/lib/pghero.rb +28 -26
- 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 +5 -4
- 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,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,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"
|
|
@@ -193,7 +201,8 @@ module PgHero
|
|
|
193
201
|
|
|
194
202
|
if @tables.any?
|
|
195
203
|
@row_counts = @database.table_stats(table: @tables).to_h { |i| [i[:table], i[:estimated_rows]] }
|
|
196
|
-
@
|
|
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
208
|
render_text "Unknown query", status: :not_found
|
|
@@ -395,6 +404,7 @@ module PgHero
|
|
|
395
404
|
redirect_backward alert: "The database user does not have permission to enable query stats"
|
|
396
405
|
end
|
|
397
406
|
|
|
407
|
+
# TODO disable if historical query stats enabled?
|
|
398
408
|
def reset_query_stats
|
|
399
409
|
success =
|
|
400
410
|
if @database.server_version_num >= 120000
|
|
@@ -440,14 +450,18 @@ module PgHero
|
|
|
440
450
|
end
|
|
441
451
|
|
|
442
452
|
def set_suggested_indexes(min_average_time = 0, min_calls = 0)
|
|
453
|
+
if @database.suggested_indexes_enabled? && !@indexes
|
|
454
|
+
@indexes, @indexes_timeout = rescue_timeout([]) { @database.indexes }
|
|
455
|
+
end
|
|
456
|
+
|
|
443
457
|
@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 })
|
|
458
|
+
if !@indexes_timeout && @database.suggested_indexes_enabled?
|
|
459
|
+
@database.suggested_indexes_by_query(query_stats: @query_stats.select { |qs| qs[:average_time] >= min_average_time && qs[:calls] >= min_calls }, indexes: @indexes)
|
|
446
460
|
else
|
|
447
461
|
{}
|
|
448
462
|
end
|
|
449
463
|
|
|
450
|
-
@suggested_indexes = @database.suggested_indexes(suggested_indexes_by_query: @suggested_indexes_by_query
|
|
464
|
+
@suggested_indexes = @database.suggested_indexes(suggested_indexes_by_query: @suggested_indexes_by_query)
|
|
451
465
|
@query_stats_by_query = @query_stats.index_by { |q| q[:query] }
|
|
452
466
|
@debug = params[:debug].present?
|
|
453
467
|
end
|
|
@@ -495,5 +509,13 @@ module PgHero
|
|
|
495
509
|
redirect_to root_path, alert: "Query stats not enabled"
|
|
496
510
|
end
|
|
497
511
|
end
|
|
512
|
+
|
|
513
|
+
# rescue QueryCanceled for case when
|
|
514
|
+
# statement timeout is less than lock timeout
|
|
515
|
+
def rescue_timeout(default)
|
|
516
|
+
[yield, false]
|
|
517
|
+
rescue ActiveRecord::LockWaitTimeout, ActiveRecord::QueryCanceled
|
|
518
|
+
[default, true]
|
|
519
|
+
end
|
|
498
520
|
end
|
|
499
521
|
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" %>
|
|
@@ -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
|
|
|
@@ -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>
|
|
@@ -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>
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
<div class="content">
|
|
2
2
|
<p id="periods">
|
|
3
3
|
<% @periods.each do |name, options| %>
|
|
4
|
-
<%= link_to name, system_path(options) %>
|
|
4
|
+
<%= link_to name, system_path(params: options) %>
|
|
5
5
|
<% end %>
|
|
6
6
|
</p>
|
|
7
|
-
<% path_options = {duration: @duration, period: @period} %>
|
|
7
|
+
<% path_options = {params: {duration: @duration, period: @period}} %>
|
|
8
8
|
|
|
9
9
|
<h1>CPU</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(cpu_usage_path(path_options)) %>, {max: 100, colors: ["#5bc0de"], suffix: "%", library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<h1>Load</h1>
|
|
16
16
|
<div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
17
17
|
<script>
|
|
18
|
-
new Chartkick.LineChart("chart-2", <%=
|
|
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
|
</script>
|
|
20
20
|
|
|
21
21
|
<h1>Connections</h1>
|
|
22
22
|
<div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
23
23
|
<script>
|
|
24
|
-
new Chartkick.LineChart("chart-3", <%=
|
|
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
|
</script>
|
|
26
26
|
|
|
27
27
|
<% if @database.replica? %>
|
|
28
28
|
<h1>Replication Lag</h1>
|
|
29
29
|
<div id="chart-4" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
30
30
|
<script>
|
|
31
|
-
new Chartkick.LineChart("chart-4", <%=
|
|
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
|
</script>
|
|
33
33
|
<% end %>
|
|
34
34
|
</div>
|
data/lib/pghero/engine.rb
CHANGED
|
@@ -5,7 +5,7 @@ module PgHero
|
|
|
5
5
|
initializer "pghero", group: :all do |app|
|
|
6
6
|
# check if Rails api mode
|
|
7
7
|
if app.config.respond_to?(:assets)
|
|
8
|
-
if defined?(Sprockets) && Sprockets::VERSION >=
|
|
8
|
+
if defined?(Sprockets) && Sprockets::VERSION.to_i >= 4
|
|
9
9
|
app.config.assets.precompile << "pghero/application.js"
|
|
10
10
|
app.config.assets.precompile << "pghero/application.css"
|
|
11
11
|
app.config.assets.precompile << "pghero/favicon.png"
|
data/lib/pghero/methods/basic.rb
CHANGED
|
@@ -112,15 +112,8 @@ module PgHero
|
|
|
112
112
|
::PgHero::Stats.connection
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
-
def insert_stats(table, columns, values)
|
|
116
|
-
values = values.map { |v| "(#{v.map { |v2| quote(v2) }.join(",")})" }.join(",")
|
|
117
|
-
columns = columns.map { |v| quote_table_name(v) }.join(",")
|
|
118
|
-
stats_connection.execute("INSERT INTO #{quote_table_name(table)} (#{columns}) VALUES #{values}")
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# from ActiveSupport
|
|
122
115
|
def squish(str)
|
|
123
|
-
str.to_s.
|
|
116
|
+
str.to_s.squish
|
|
124
117
|
end
|
|
125
118
|
|
|
126
119
|
def add_source(sql)
|
|
@@ -135,6 +128,10 @@ module PgHero
|
|
|
135
128
|
connection.quote_table_name(value)
|
|
136
129
|
end
|
|
137
130
|
|
|
131
|
+
def quote_column_name(value)
|
|
132
|
+
connection.quote_column_name(value)
|
|
133
|
+
end
|
|
134
|
+
|
|
138
135
|
def unquote(part)
|
|
139
136
|
if part && part.start_with?('"')
|
|
140
137
|
part[1..-2]
|
|
@@ -3,7 +3,7 @@ module PgHero
|
|
|
3
3
|
module Connections
|
|
4
4
|
def connections
|
|
5
5
|
if server_version_num >= 90500
|
|
6
|
-
select_all
|
|
6
|
+
select_all <<~SQL
|
|
7
7
|
SELECT
|
|
8
8
|
pg_stat_activity.pid,
|
|
9
9
|
datname AS database,
|
|
@@ -20,7 +20,7 @@ module PgHero
|
|
|
20
20
|
pg_stat_activity.pid
|
|
21
21
|
SQL
|
|
22
22
|
else
|
|
23
|
-
select_all
|
|
23
|
+
select_all <<~SQL
|
|
24
24
|
SELECT
|
|
25
25
|
pid,
|
|
26
26
|
datname AS database,
|
|
@@ -41,7 +41,7 @@ module PgHero
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def connection_states
|
|
44
|
-
states = select_all
|
|
44
|
+
states = select_all <<~SQL
|
|
45
45
|
SELECT
|
|
46
46
|
state,
|
|
47
47
|
COUNT(*) AS connections
|
|
@@ -57,7 +57,7 @@ module PgHero
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def connection_sources
|
|
60
|
-
select_all
|
|
60
|
+
select_all <<~SQL
|
|
61
61
|
SELECT
|
|
62
62
|
datname AS database,
|
|
63
63
|
usename AS user,
|
|
@@ -2,7 +2,7 @@ module PgHero
|
|
|
2
2
|
module Methods
|
|
3
3
|
module Indexes
|
|
4
4
|
def index_hit_rate
|
|
5
|
-
select_one
|
|
5
|
+
select_one <<~SQL
|
|
6
6
|
SELECT
|
|
7
7
|
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read), 0) AS rate
|
|
8
8
|
FROM
|
|
@@ -11,7 +11,7 @@ module PgHero
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def index_caching
|
|
14
|
-
select_all
|
|
14
|
+
select_all <<~SQL
|
|
15
15
|
SELECT
|
|
16
16
|
schemaname AS schema,
|
|
17
17
|
relname AS table,
|
|
@@ -29,7 +29,7 @@ module PgHero
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def index_usage
|
|
32
|
-
select_all
|
|
32
|
+
select_all <<~SQL
|
|
33
33
|
SELECT
|
|
34
34
|
schemaname AS schema,
|
|
35
35
|
relname AS table,
|
|
@@ -47,7 +47,7 @@ module PgHero
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def missing_indexes
|
|
50
|
-
select_all
|
|
50
|
+
select_all <<~SQL
|
|
51
51
|
SELECT
|
|
52
52
|
schemaname AS schema,
|
|
53
53
|
relname AS table,
|
|
@@ -69,7 +69,7 @@ module PgHero
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def unused_indexes(max_scans: 50, across: [])
|
|
72
|
-
result = select_all_size
|
|
72
|
+
result = select_all_size <<~SQL
|
|
73
73
|
SELECT
|
|
74
74
|
schemaname AS schema,
|
|
75
75
|
relname AS table,
|
|
@@ -104,7 +104,7 @@ module PgHero
|
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
def last_stats_reset_time
|
|
107
|
-
select_one
|
|
107
|
+
select_one <<~SQL
|
|
108
108
|
SELECT
|
|
109
109
|
pg_stat_get_db_stat_reset_time(oid) AS reset_time
|
|
110
110
|
FROM
|
|
@@ -126,7 +126,7 @@ module PgHero
|
|
|
126
126
|
# TODO parse array properly
|
|
127
127
|
# https://stackoverflow.com/questions/2204058/list-columns-with-indexes-in-postgresql
|
|
128
128
|
def indexes
|
|
129
|
-
indexes = select_all(
|
|
129
|
+
indexes = select_all(<<~SQL
|
|
130
130
|
SELECT
|
|
131
131
|
schemaname AS schema,
|
|
132
132
|
t.relname AS table,
|
|
@@ -186,7 +186,7 @@ module PgHero
|
|
|
186
186
|
# thanks @jberkus and @mbanck
|
|
187
187
|
def index_bloat(min_size: nil)
|
|
188
188
|
min_size ||= index_bloat_bytes
|
|
189
|
-
select_all
|
|
189
|
+
select_all <<~SQL
|
|
190
190
|
WITH btree_index_atts AS (
|
|
191
191
|
SELECT
|
|
192
192
|
nspname, relname, reltuples, relpages, indrelid, relam,
|