pghero_fork 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +391 -0
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +3 -0
  6. data/app/assets/images/pghero/favicon.png +0 -0
  7. data/app/assets/javascripts/pghero/Chart.bundle.js +20755 -0
  8. data/app/assets/javascripts/pghero/application.js +158 -0
  9. data/app/assets/javascripts/pghero/chartkick.js +2436 -0
  10. data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
  11. data/app/assets/javascripts/pghero/jquery.js +10872 -0
  12. data/app/assets/javascripts/pghero/nouislider.js +2672 -0
  13. data/app/assets/stylesheets/pghero/application.css +514 -0
  14. data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
  15. data/app/assets/stylesheets/pghero/nouislider.css +310 -0
  16. data/app/controllers/pg_hero/home_controller.rb +449 -0
  17. data/app/helpers/pg_hero/home_helper.rb +30 -0
  18. data/app/views/layouts/pg_hero/application.html.erb +68 -0
  19. data/app/views/pg_hero/home/_connections_table.html.erb +16 -0
  20. data/app/views/pg_hero/home/_live_queries_table.html.erb +51 -0
  21. data/app/views/pg_hero/home/_queries_table.html.erb +72 -0
  22. data/app/views/pg_hero/home/_query_stats_slider.html.erb +16 -0
  23. data/app/views/pg_hero/home/_suggested_index.html.erb +18 -0
  24. data/app/views/pg_hero/home/connections.html.erb +32 -0
  25. data/app/views/pg_hero/home/explain.html.erb +27 -0
  26. data/app/views/pg_hero/home/index.html.erb +518 -0
  27. data/app/views/pg_hero/home/index_bloat.html.erb +72 -0
  28. data/app/views/pg_hero/home/live_queries.html.erb +11 -0
  29. data/app/views/pg_hero/home/maintenance.html.erb +55 -0
  30. data/app/views/pg_hero/home/queries.html.erb +33 -0
  31. data/app/views/pg_hero/home/relation_space.html.erb +14 -0
  32. data/app/views/pg_hero/home/show_query.html.erb +106 -0
  33. data/app/views/pg_hero/home/space.html.erb +83 -0
  34. data/app/views/pg_hero/home/system.html.erb +34 -0
  35. data/app/views/pg_hero/home/tune.html.erb +53 -0
  36. data/config/routes.rb +32 -0
  37. data/lib/generators/pghero/config_generator.rb +13 -0
  38. data/lib/generators/pghero/query_stats_generator.rb +18 -0
  39. data/lib/generators/pghero/space_stats_generator.rb +18 -0
  40. data/lib/generators/pghero/templates/config.yml.tt +46 -0
  41. data/lib/generators/pghero/templates/query_stats.rb.tt +15 -0
  42. data/lib/generators/pghero/templates/space_stats.rb.tt +13 -0
  43. data/lib/pghero.rb +246 -0
  44. data/lib/pghero/connection.rb +5 -0
  45. data/lib/pghero/database.rb +175 -0
  46. data/lib/pghero/engine.rb +16 -0
  47. data/lib/pghero/methods/basic.rb +160 -0
  48. data/lib/pghero/methods/connections.rb +77 -0
  49. data/lib/pghero/methods/constraints.rb +30 -0
  50. data/lib/pghero/methods/explain.rb +29 -0
  51. data/lib/pghero/methods/indexes.rb +332 -0
  52. data/lib/pghero/methods/kill.rb +28 -0
  53. data/lib/pghero/methods/maintenance.rb +93 -0
  54. data/lib/pghero/methods/queries.rb +75 -0
  55. data/lib/pghero/methods/query_stats.rb +349 -0
  56. data/lib/pghero/methods/replication.rb +74 -0
  57. data/lib/pghero/methods/sequences.rb +124 -0
  58. data/lib/pghero/methods/settings.rb +37 -0
  59. data/lib/pghero/methods/space.rb +141 -0
  60. data/lib/pghero/methods/suggested_indexes.rb +329 -0
  61. data/lib/pghero/methods/system.rb +287 -0
  62. data/lib/pghero/methods/tables.rb +68 -0
  63. data/lib/pghero/methods/users.rb +87 -0
  64. data/lib/pghero/query_stats.rb +5 -0
  65. data/lib/pghero/space_stats.rb +5 -0
  66. data/lib/pghero/stats.rb +6 -0
  67. data/lib/pghero/version.rb +3 -0
  68. data/lib/tasks/pghero.rake +27 -0
  69. data/licenses/LICENSE-chart.js.txt +9 -0
  70. data/licenses/LICENSE-chartkick.js.txt +22 -0
  71. data/licenses/LICENSE-highlight.js.txt +29 -0
  72. data/licenses/LICENSE-jquery.txt +20 -0
  73. data/licenses/LICENSE-moment.txt +22 -0
  74. data/licenses/LICENSE-nouislider.txt +21 -0
  75. metadata +130 -0
@@ -0,0 +1,72 @@
1
+ <div class="content">
2
+ <h1>Index Bloat</h1>
3
+
4
+ <% if @index_bloat.any? %>
5
+
6
+ <p>Indexes can become <%= link_to "bloated over time", "https://www.compose.com/articles/postgresql-bloat-origins-monitoring-and-managing/", target: "_blank" %>. Recreate them to remove bloat.</p>
7
+
8
+ <p>For each index, run:</p>
9
+ <pre><code>CREATE INDEX CONCURRENTLY new_index ...;
10
+ ANALYZE table;
11
+ DROP INDEX CONCURRENTLY index;
12
+ ANALYZE table;
13
+ ALTER INDEX new_index RENAME TO index;</code></pre>
14
+
15
+ <p>
16
+ <% if @show_sql %>
17
+ <%= link_to "Hide SQL", {} %>
18
+ <% else %>
19
+ <%= link_to "Show SQL", {sql: "t"} %>
20
+ <% end %>
21
+ for each index
22
+ </p>
23
+
24
+ <table class="table">
25
+ <thead>
26
+ <tr>
27
+ <th>Index</th>
28
+ <th style="width: 15%;">Bloat</th>
29
+ <th style="width: 15%;">Size</th>
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ <% @index_bloat.each do |index| %>
34
+ <tr>
35
+ <td>
36
+ <span style="word-break: break-all;"><%= index[:index] %></span>
37
+ <% if index[:primary] %>
38
+ <span class="primary-key">PRIMARY</span>
39
+ <% end %>
40
+ </td>
41
+ <td><%= PgHero.pretty_size(index[:bloat_bytes]) %></td>
42
+ <td><%= PgHero.pretty_size(index[:index_bytes]) %></td>
43
+ </tr>
44
+ <% if @show_sql && !index[:primary] %>
45
+ <tr>
46
+ <td colspan="3" style="border-top: none; padding: 0;">
47
+ <% new_index = "new_#{index[:index]}".first(63) %>
48
+ <pre><code><%= index[:definition].sub(" INDEX ", " INDEX CONCURRENTLY \n ").sub(index[:index], new_index) %>;
49
+
50
+ ANALYZE <%= pghero_pretty_ident(index[:table], schema: index[:schema]) %>;
51
+
52
+ DROP INDEX CONCURRENTLY
53
+ <%= pghero_pretty_ident(index[:index]) %>;
54
+
55
+ ANALYZE <%= pghero_pretty_ident(index[:table], schema: index[:schema]) %>;
56
+
57
+ ALTER INDEX <%= pghero_pretty_ident(new_index) %>
58
+ RENAME TO <%= pghero_pretty_ident(index[:index]) %>;</code></pre>
59
+ </td>
60
+ </tr>
61
+ <% end %>
62
+ <% end %>
63
+ </tbody>
64
+ </table>
65
+ <% else %>
66
+ <p>No significant index bloat!</p>
67
+ <% end %>
68
+ </div>
69
+
70
+ <script>
71
+ highlightQueries();
72
+ </script>
@@ -0,0 +1,11 @@
1
+ <div class="content">
2
+ <h1>Live Queries</h1>
3
+
4
+ <p><%= pluralize(@running_queries.size, "query") %></p>
5
+
6
+ <%= render partial: "live_queries_table", locals: {queries: @running_queries, vacuum_progress: @vacuum_progress} %>
7
+
8
+ <p><%= button_to "Kill all connections", kill_all_path, class: "btn btn-danger" %></p>
9
+
10
+ <p class="text-muted">You may need to restart your app servers afterwards.</p>
11
+ </div>
@@ -0,0 +1,55 @@
1
+ <div class="content">
2
+ <h1>Maintenance</h1>
3
+
4
+ <table class="table">
5
+ <thead>
6
+ <tr>
7
+ <th>Table</th>
8
+ <th style="width: 20%;">Last Vacuum</th>
9
+ <th style="width: 20%;">Last Analyze</th>
10
+ <% if @show_dead_rows %>
11
+ <th style="width: 20%;">Dead Rows</th>
12
+ <% end %>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% @maintenance_info.each do |table| %>
17
+ <tr>
18
+ <td>
19
+ <%= table[:table] %>
20
+ <% if table[:schema] != "public" %>
21
+ <span class="text-muted"><%= table[:schema] %></span>
22
+ <% end %>
23
+ </td>
24
+ <td>
25
+ <% time = [table[:last_autovacuum], table[:last_vacuum]].compact.max %>
26
+ <% if time %>
27
+ <%= l time.in_time_zone(@time_zone), format: :short %>
28
+ <% else %>
29
+ <span class="text-muted">Unknown</span>
30
+ <% end %>
31
+ </td>
32
+ <td>
33
+ <% time = [table[:last_autoanalyze], table[:last_analyze]].compact.max %>
34
+ <% if time %>
35
+ <%= l time.in_time_zone(@time_zone), format: :short %>
36
+ <% else %>
37
+ <span class="text-muted">Unknown</span>
38
+ <% end %>
39
+ </td>
40
+ <% if @show_dead_rows %>
41
+ <td>
42
+ <% if table[:live_rows] != 0 %>
43
+ <%# use live rows only for denominator to make it easier to compare with autovacuum_vacuum_scale_factor %>
44
+ <%# it's not a true percentage, since it can go above 100% %>
45
+ <%= (100.0 * table[:dead_rows] / table[:live_rows]).round %>%
46
+ <% else %>
47
+ <span class="text-muted">Unknown</span>
48
+ <% end %>
49
+ </td>
50
+ <% end %>
51
+ </tr>
52
+ <% end %>
53
+ </tbody>
54
+ </table>
55
+ </div>
@@ -0,0 +1,33 @@
1
+ <div class="content">
2
+ <% if @query_stats_enabled %>
3
+ <%= button_to "Reset", reset_query_stats_path, class: "btn btn-danger", style: "float: right;" %>
4
+ <% end %>
5
+
6
+ <h1 style="float: left;">Queries</h1>
7
+
8
+ <% if @historical_query_stats_enabled %>
9
+ <%= render partial: "query_stats_slider" %>
10
+ <% elsif @database.query_stats_table_exists? && (columns = @database.missing_query_stats_columns).any? %>
11
+ <div style="clear: both;">
12
+ <p>Add missing columns to re-enable historical query stats.</p>
13
+ <pre><code><% @database.missing_query_stats_columns.each do |column| %>ALTER TABLE pghero_query_stats ADD COLUMN "<%= column %>" <%= column == "query_hash" ? "bigint" : "text" %>;
14
+ <% end %></code></pre>
15
+ <p>Then restart the web server.</p>
16
+ </div>
17
+ <% end %>
18
+
19
+ <% if @query_stats_enabled %>
20
+ <% if @error %>
21
+ <div class="alert alert-danger">Cannot understand start or end time.</div>
22
+ <% elsif @query_stats.any? || @historical_query_stats_enabled %>
23
+ <%= render partial: "queries_table", locals: {queries: @query_stats, sort_headers: true} %>
24
+ <script>
25
+ highlightQueries();
26
+ </script>
27
+ <% else %>
28
+ <p>Stats are not available yet. Come back soon!</p>
29
+ <% end %>
30
+ <% else %>
31
+ <p>Query stats are not enabled.</p>
32
+ <% end %>
33
+ </div>
@@ -0,0 +1,14 @@
1
+ <div class="content">
2
+ <h1>
3
+ <%= @relation %>
4
+ <% if @schema != "public" %>
5
+ <small><%= @schema %></small>
6
+ <% end %>
7
+ </h1>
8
+
9
+ <h1>Size</h1>
10
+ <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
11
+ <script>
12
+ new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, min: null, bytes: true, library: {tooltips: {intersect: false, mode: "index"}}})
13
+ </script>
14
+ </div>
@@ -0,0 +1,106 @@
1
+ <div class="content">
2
+ <pre><code style="max-height: 230px; overflow: hidden;" onclick="this.style.maxHeight = 'none';"><%= @query %></code></pre>
3
+ <script>
4
+ highlightQueries()
5
+ </script>
6
+
7
+ <% if @explainable_query %>
8
+ <p>
9
+ <%= button_to "Explain", explain_path, params: {query: @explainable_query}, form: {target: "_blank"}, class: "btn btn-info" %>
10
+ </p>
11
+ <% end %>
12
+
13
+ <% if @origins && @origins.keys.select { |k| k.length > 0 }.any? %>
14
+ <table style="table-layout: auto;">
15
+ <thead>
16
+ <tr>
17
+ <th colspan="2">
18
+ <div style="float: right;">Approx. Time</div>
19
+ Origin
20
+ </th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% @origins.sort_by { |o, c| [-c, o.to_s] }.each do |origin, count| %>
25
+ <tr>
26
+ <td class="origin" style="width: 90%;">
27
+ <% if origin.length > 0 %>
28
+ <%= origin %>
29
+ <% else %>
30
+ <span class="text-muted">Unknown</span>
31
+ <% end %>
32
+ </td>
33
+ <td style="text-align: right; width: 10%;">
34
+ <% pct = (100.0 * count / @total_count).round %>
35
+ <% if pct == 0 %>
36
+ &lt; 1%
37
+ <% else %>
38
+ <%= pct %>%
39
+ <% end %>
40
+ </td>
41
+ </tr>
42
+ <% end %>
43
+ </tbody>
44
+ </table>
45
+ <% end %>
46
+
47
+ <!-- chart -->
48
+ <% if @chart_data %>
49
+ <h1>Total Time <small>ms</small></h1>
50
+ <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
51
+ <script>
52
+ new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
53
+ </script>
54
+
55
+ <h1>Average Time <small>ms</small></h1>
56
+ <div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
57
+ <script>
58
+ new Chartkick.LineChart("chart-2", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
59
+ </script>
60
+
61
+ <h1>Calls</h1>
62
+ <div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
63
+ <script>
64
+ new Chartkick.LineChart("chart-3", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
65
+ </script>
66
+ <% else %>
67
+ <p>
68
+ Enable
69
+ <%= link_to "historical query stats", "https://github.com/ankane/pghero", target: "_blank" %>
70
+ to see more details
71
+ </p>
72
+ <% end %>
73
+
74
+ <!-- table info -->
75
+ <% if @tables.any? %>
76
+ <h1>Tables</h1>
77
+ <table>
78
+ <thead>
79
+ <tr>
80
+ <th style="width: 25%;">Name</th>
81
+ <th style="width: 25%;">Rows</th>
82
+ <th>Indexes</th>
83
+ </tr>
84
+ </thead>
85
+ <tbody>
86
+ <% @tables.each do |table| %>
87
+ <tr>
88
+ <td><%= table %></td>
89
+ <td><%= @row_counts[table] %></td>
90
+ <td>
91
+ <ul>
92
+ <% @indexes_by_table[table].to_a.sort_by { |i| [i[:primary] ? 0 : 1, i[:columns]] }.each do |i3| %>
93
+ <li>
94
+ <%= i3[:columns].join(", ") %><% if i3[:using] != "btree" %>
95
+ <%= i3[:using].to_s.upcase %><% end %>
96
+ <% if i3[:primary] %> PRIMARY<% elsif i3[:unique] %> UNIQUE<% end %>
97
+ </li>
98
+ <% end %>
99
+ </ul>
100
+ </td>
101
+ </tr>
102
+ <% end %>
103
+ </tbody>
104
+ </table>
105
+ <% end %>
106
+ </div>
@@ -0,0 +1,83 @@
1
+ <div class="content">
2
+ <h1>Space</h1>
3
+
4
+ <p>Database Size: <%= @database_size %></p>
5
+
6
+ <% if @system_stats_enabled %>
7
+ <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
8
+ <script>
9
+ new Chartkick.LineChart("chart-1", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"], bytes: true, library: {tooltips: {intersect: false, mode: "index"}}})
10
+ </script>
11
+ <% end %>
12
+
13
+ <!--
14
+ <% if @index_bloat.any? %>
15
+ <p>Check out <%= link_to "index bloat", index_bloat_path %> for an easy way to reclaim space.</p>
16
+ <% end %>
17
+ -->
18
+
19
+ <% if @unused_indexes.any? %>
20
+ <p>
21
+ <%= pluralize(@unused_indexes.size, "unused index") %>. Remove them
22
+ <% if @show_migrations %>
23
+ <a href="javascript: void(0);" onclick="document.getElementById('migration').style.display = 'block';">with a migration</a>
24
+ <% end %>
25
+ for faster writes.
26
+
27
+ <% if @database.replicating? %>
28
+ Check they aren’t used on replicas.
29
+ <% end %>
30
+ </p>
31
+
32
+ <div id="migration" style="display: none;">
33
+ <pre>rails generate migration remove_unused_indexes</pre>
34
+ <p>And paste</p>
35
+ <pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.sort_by { |q| [-q[:size_bytes], q[:index]] }.each do |query| %>
36
+ <%= pghero_remove_index(query) %><% end %></pre>
37
+ </div>
38
+ <% end %>
39
+
40
+ <table class="table space-table">
41
+ <thead>
42
+ <tr>
43
+ <th><%= link_to "Relation", {sort: "name"} %></th>
44
+ <th style="width: 15%;"><%= link_to "Size", {} %></th>
45
+ <% if @space_stats_enabled %>
46
+ <th style="width: 15%;"><%= link_to "#{@days}d Growth", {sort: "growth"} %></th>
47
+ <% end %>
48
+ </tr>
49
+ </thead>
50
+ <tbody>
51
+ <% @relation_sizes.each do |query| %>
52
+ <tr>
53
+ <td style="<%= query[:type] == "index" ? "font-style: italic;" : "" %>">
54
+ <span style="word-break: break-all;">
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>
70
+ <% if @space_stats_enabled %>
71
+ <td>
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>
78
+ <% end %>
79
+ </tr>
80
+ <% end %>
81
+ </tbody>
82
+ </table>
83
+ </div>
@@ -0,0 +1,34 @@
1
+ <div class="content">
2
+ <p id="periods">
3
+ <% @periods.each do |name, options| %>
4
+ <%= link_to name, system_path(options) %>
5
+ <% end %>
6
+ </p>
7
+ <% path_options = {duration: @duration, period: @period} %>
8
+
9
+ <h1>CPU</h1>
10
+ <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
11
+ <script>
12
+ new Chartkick.LineChart("chart-1", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"], suffix: "%", library: {tooltips: {intersect: false, mode: "index"}}})
13
+ </script>
14
+
15
+ <h1>Load</h1>
16
+ <div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
17
+ <script>
18
+ new Chartkick.LineChart("chart-2", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"], library: {tooltips: {intersect: false, mode: "nearest"}}})
19
+ </script>
20
+
21
+ <h1>Connections</h1>
22
+ <div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
23
+ <script>
24
+ new Chartkick.LineChart("chart-3", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {tooltips: {intersect: false, mode: "index"}}})
25
+ </script>
26
+
27
+ <% if @database.replica? %>
28
+ <h1>Replication Lag</h1>
29
+ <div id="chart-4" class="chart" style="margin-bottom: 20px;">Loading...</div>
30
+ <script>
31
+ new Chartkick.LineChart("chart-4", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {tooltips: {intersect: false, mode: "index"}}})
32
+ </script>
33
+ <% end %>
34
+ </div>
@@ -0,0 +1,53 @@
1
+ <div class="content">
2
+ <h1>Tune</h1>
3
+
4
+ <table class="table">
5
+ <thead>
6
+ <tr>
7
+ <th>Setting</th>
8
+ <th style="width: 20%;">Value</th>
9
+ </tr>
10
+ </thead>
11
+ <tbody>
12
+ <% @settings.each do |setting, value| %>
13
+ <tr>
14
+ <td><%= setting %></td>
15
+ <td><%= value %></td>
16
+ </tr>
17
+ <% end %>
18
+ </tbody>
19
+ </table>
20
+
21
+ <% version_parts = @database.server_version.split(" ").first.split(".") %>
22
+ <p>Check out <%= link_to "PgTune", "https://pgtune.leopard.in.ua/", target: "_blank" %> for recommendations. DB version is <%= version_parts[0].to_i >= 10 ? version_parts[0] : version_parts.first(2).join(".") %>.</p>
23
+ </div>
24
+
25
+ <% if @autovacuum_settings %>
26
+ <div class="content">
27
+ <h1>Autovacuum</h1>
28
+
29
+ <table class="table">
30
+ <thead>
31
+ <tr>
32
+ <th>Setting</th>
33
+ <th style="width: 20%;">Value</th>
34
+ </tr>
35
+ </thead>
36
+ <tbody>
37
+ <% @autovacuum_settings.each do |setting, value| %>
38
+ <tr>
39
+ <td><%= setting %></td>
40
+ <td>
41
+ <%= value %>
42
+ <% if setting == :autovacuum_vacuum_cost_limit && value == "-1" %>
43
+ <span class="text-muted"><%= @database.vacuum_settings[:vacuum_cost_limit] %></span>
44
+ <% end %>
45
+ </td>
46
+ </tr>
47
+ <% end %>
48
+ </tbody>
49
+ </table>
50
+
51
+ <p>Check out <%= link_to "Autovacuum Tuning Basics", "https://blog.2ndquadrant.com/autovacuum-tuning-basics/", target: "_blank" %> for recommendations.</p>
52
+ </div>
53
+ <% end %>