pghero 3.1.0 → 3.3.3

Sign up to get free protection for your applications and to get access to all the features.
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,