pghero 3.1.0 → 3.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -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 +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 +41 -12
- 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 +11 -6
- data/lib/pghero/methods/system.rb +10 -4
- 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
|
@@ -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},
|
@@ -395,6 +411,7 @@ module PgHero
|
|
395
411
|
redirect_backward alert: "The database user does not have permission to enable query stats"
|
396
412
|
end
|
397
413
|
|
414
|
+
# TODO disable if historical query stats enabled?
|
398
415
|
def reset_query_stats
|
399
416
|
success =
|
400
417
|
if @database.server_version_num >= 120000
|
@@ -440,14 +457,18 @@ module PgHero
|
|
440
457
|
end
|
441
458
|
|
442
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
|
+
|
443
464
|
@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 })
|
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)
|
446
467
|
else
|
447
468
|
{}
|
448
469
|
end
|
449
470
|
|
450
|
-
@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)
|
451
472
|
@query_stats_by_query = @query_stats.index_by { |q| q[:query] }
|
452
473
|
@debug = params[:debug].present?
|
453
474
|
end
|
@@ -495,5 +516,13 @@ module PgHero
|
|
495
516
|
redirect_to root_path, alert: "Query stats not enabled"
|
496
517
|
end
|
497
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
|
498
527
|
end
|
499
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" %>
|
@@ -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,
|