pghero 3.1.0 → 3.3.3

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -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 +440 -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 +41 -12
  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 +11 -6
  37. data/lib/pghero/methods/system.rb +10 -4
  38. data/lib/pghero/methods/tables.rb +4 -5
  39. data/lib/pghero/version.rb +1 -1
  40. data/lib/pghero.rb +28 -26
  41. data/lib/tasks/pghero.rake +11 -1
  42. data/licenses/LICENSE-chart.js.txt +1 -1
  43. data/licenses/LICENSE-date-fns.txt +21 -20
  44. data/licenses/LICENSE-kurkle-color.txt +9 -0
  45. metadata +5 -4
  46. 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
@@ -239,9 +248,16 @@ module PgHero
239
248
  stats =
240
249
  case @database.system_stats_provider
241
250
  when :azure
242
- [
243
- {name: "IO Consumption", data: @database.azure_stats("io_consumption_percent", **system_params), library: chart_library_options}
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, indexes: @indexes)
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 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,