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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/app/assets/javascripts/pghero/Chart.bundle.js +23379 -19766
  4. data/app/assets/javascripts/pghero/application.js +13 -12
  5. data/app/assets/javascripts/pghero/chartkick.js +834 -764
  6. data/app/assets/javascripts/pghero/highlight.min.js +373 -0
  7. data/app/assets/javascripts/pghero/jquery.js +318 -197
  8. data/app/assets/javascripts/pghero/nouislider.js +676 -1066
  9. data/app/assets/stylesheets/pghero/application.css +8 -2
  10. data/app/assets/stylesheets/pghero/nouislider.css +4 -10
  11. data/app/controllers/pg_hero/home_controller.rb +31 -9
  12. data/app/helpers/pg_hero/home_helper.rb +2 -2
  13. data/app/views/layouts/pg_hero/application.html.erb +1 -1
  14. data/app/views/pg_hero/home/_query_stats_slider.html.erb +6 -6
  15. data/app/views/pg_hero/home/connections.html.erb +6 -6
  16. data/app/views/pg_hero/home/index.html.erb +3 -1
  17. data/app/views/pg_hero/home/queries.html.erb +4 -2
  18. data/app/views/pg_hero/home/relation_space.html.erb +1 -1
  19. data/app/views/pg_hero/home/show_query.html.erb +16 -12
  20. data/app/views/pg_hero/home/space.html.erb +44 -40
  21. data/app/views/pg_hero/home/system.html.erb +6 -6
  22. data/lib/generators/pghero/query_stats_generator.rb +1 -0
  23. data/lib/generators/pghero/space_stats_generator.rb +1 -0
  24. data/lib/pghero/engine.rb +1 -1
  25. data/lib/pghero/methods/basic.rb +5 -8
  26. data/lib/pghero/methods/connections.rb +4 -4
  27. data/lib/pghero/methods/constraints.rb +1 -1
  28. data/lib/pghero/methods/indexes.rb +8 -8
  29. data/lib/pghero/methods/kill.rb +1 -1
  30. data/lib/pghero/methods/maintenance.rb +3 -3
  31. data/lib/pghero/methods/queries.rb +2 -2
  32. data/lib/pghero/methods/query_stats.rb +19 -19
  33. data/lib/pghero/methods/replication.rb +2 -2
  34. data/lib/pghero/methods/sequences.rb +2 -2
  35. data/lib/pghero/methods/space.rb +18 -12
  36. data/lib/pghero/methods/suggested_indexes.rb +10 -6
  37. data/lib/pghero/methods/tables.rb +4 -5
  38. data/lib/pghero/version.rb +1 -1
  39. data/lib/pghero.rb +28 -26
  40. data/lib/tasks/pghero.rake +11 -1
  41. data/licenses/LICENSE-chart.js.txt +1 -1
  42. data/licenses/LICENSE-date-fns.txt +21 -20
  43. data/licenses/LICENSE-kurkle-color.txt +9 -0
  44. metadata +5 -4
  45. 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 140px 20px 140px;
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-bottom: 20px;
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
- top: -17px;
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
- @readable_sequences, @unreadable_sequences = @database.sequences.partition { |s| s[:readable] }
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 = @database.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
- @indexes = @database.indexes
161
- set_suggested_indexes
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
- @indexes_by_table = @database.indexes.group_by { |i| i[:table] }
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, indexes: @indexes)
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 pghero_js_var(name, value)
16
- "var #{name} = #{json_escape(value.to_json(root: false))};".html_safe
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.pack", "pghero/application" %>
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
- <%= pghero_js_var("sort", @sort) %>
9
- <%= pghero_js_var("minAverageTime", @min_average_time) %>
10
- <%= pghero_js_var("minCalls", @min_calls) %>
11
- <%= pghero_js_var("debug", @debug) %>
12
- <%= pghero_js_var("startAt", params[:start_at] ? @start_at.to_i * 1000 : nil) %>
13
- <%= pghero_js_var("endAt", @end_at.to_i * 1000) %>
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" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
9
+ <div id="chart-1" class="chart pie-chart">Loading...</div>
10
10
  <script>
11
- new Chartkick.PieChart("chart-1", <%= json_escape(@connections_by_database.to_json).html_safe %>);
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" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
16
+ <div id="chart-2" class="chart pie-chart">Loading...</div>
17
17
  <script>
18
- new Chartkick.PieChart("chart-2", <%= json_escape(@connections_by_user.to_json).html_safe %>);
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" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
24
+ <div id="chart-3" class="chart pie-chart">Loading...</div>
25
25
  <script>
26
- new Chartkick.PieChart("chart-3", <%= json_escape(@connections_by_ssl_status.to_json).html_safe %>);
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
- <h1 style="float: left;">Queries</h1>
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", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, min: null, bytes: true, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
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", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
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", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
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", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
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
- <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>
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", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"], bytes: true, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
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
- <table class="table space-table">
41
- <thead>
42
- <tr>
43
- <th><%= link_to (@only_tables ? "Table" : "Relation"), @header_options.merge(sort: "name") %></th>
44
- <th style="width: 15%;"><%= link_to "Size", @header_options %></th>
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
- <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>
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
- <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>
49
+ <th style="width: 15%;"><%= link_to "#{@days}d Growth", @header_options.merge(sort: "growth") %></th>
78
50
  <% end %>
79
51
  </tr>
80
- <% end %>
81
- </tbody>
82
- </table>
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", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"], suffix: "%", library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
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", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"], library: {plugins: {tooltip: {intersect: false, mode: "nearest"}}}})
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", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
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", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
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>
@@ -1,3 +1,4 @@
1
+ require "rails/generators"
1
2
  require "rails/generators/active_record"
2
3
 
3
4
  module Pghero
@@ -1,3 +1,4 @@
1
+ require "rails/generators"
1
2
  require "rails/generators/active_record"
2
3
 
3
4
  module Pghero
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 >= "4"
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"
@@ -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.gsub(/\A[[:space:]]+/, "").gsub(/[[:space:]]+\z/, "").gsub(/[[:space:]]+/, " ")
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 <<-SQL
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 <<-SQL
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 <<-SQL
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 <<-SQL
60
+ select_all <<~SQL
61
61
  SELECT
62
62
  datname AS database,
63
63
  usename AS user,
@@ -4,7 +4,7 @@ module PgHero
4
4
  # referenced fields can be nil
5
5
  # as not all constraints are foreign keys
6
6
  def invalid_constraints
7
- select_all <<-SQL
7
+ select_all <<~SQL
8
8
  SELECT
9
9
  nsp.nspname AS schema,
10
10
  rel.relname AS table,
@@ -2,7 +2,7 @@ module PgHero
2
2
  module Methods
3
3
  module Indexes
4
4
  def index_hit_rate
5
- select_one <<-SQL
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 <<-SQL
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 <<-SQL
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 <<-SQL
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 <<-SQL
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 <<-SQL
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(<<-SQL
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 <<-SQL
189
+ select_all <<~SQL
190
190
  WITH btree_index_atts AS (
191
191
  SELECT
192
192
  nspname, relname, reltuples, relpages, indrelid, relam,