pghero 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/app/assets/javascripts/pghero/application.js +13 -12
  4. data/app/assets/javascripts/pghero/highlight.min.js +373 -0
  5. data/app/assets/javascripts/pghero/jquery.js +318 -197
  6. data/app/assets/javascripts/pghero/nouislider.js +676 -1066
  7. data/app/assets/stylesheets/pghero/application.css +6 -0
  8. data/app/assets/stylesheets/pghero/nouislider.css +4 -10
  9. data/app/controllers/pg_hero/home_controller.rb +30 -9
  10. data/app/helpers/pg_hero/home_helper.rb +2 -2
  11. data/app/views/layouts/pg_hero/application.html.erb +1 -1
  12. data/app/views/pg_hero/home/_query_stats_slider.html.erb +6 -6
  13. data/app/views/pg_hero/home/connections.html.erb +6 -6
  14. data/app/views/pg_hero/home/index.html.erb +3 -1
  15. data/app/views/pg_hero/home/relation_space.html.erb +1 -1
  16. data/app/views/pg_hero/home/show_query.html.erb +16 -12
  17. data/app/views/pg_hero/home/space.html.erb +44 -40
  18. data/app/views/pg_hero/home/system.html.erb +6 -6
  19. data/lib/generators/pghero/query_stats_generator.rb +1 -0
  20. data/lib/generators/pghero/space_stats_generator.rb +1 -0
  21. data/lib/pghero/engine.rb +1 -1
  22. data/lib/pghero/methods/basic.rb +5 -8
  23. data/lib/pghero/methods/connections.rb +4 -4
  24. data/lib/pghero/methods/constraints.rb +1 -1
  25. data/lib/pghero/methods/indexes.rb +8 -8
  26. data/lib/pghero/methods/kill.rb +1 -1
  27. data/lib/pghero/methods/maintenance.rb +3 -3
  28. data/lib/pghero/methods/queries.rb +2 -2
  29. data/lib/pghero/methods/query_stats.rb +16 -17
  30. data/lib/pghero/methods/replication.rb +2 -2
  31. data/lib/pghero/methods/sequences.rb +2 -2
  32. data/lib/pghero/methods/space.rb +15 -10
  33. data/lib/pghero/methods/suggested_indexes.rb +2 -2
  34. data/lib/pghero/methods/tables.rb +4 -5
  35. data/lib/pghero/version.rb +1 -1
  36. data/lib/pghero.rb +2 -0
  37. metadata +3 -3
  38. data/app/assets/javascripts/pghero/highlight.pack.js +0 -2
@@ -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
@@ -441,14 +450,18 @@ module PgHero
441
450
  end
442
451
 
443
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
+
444
457
  @suggested_indexes_by_query =
445
- if @database.suggested_indexes_enabled?
446
- @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)
447
460
  else
448
461
  {}
449
462
  end
450
463
 
451
- @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)
452
465
  @query_stats_by_query = @query_stats.index_by { |q| q[:query] }
453
466
  @debug = params[:debug].present?
454
467
  end
@@ -496,5 +509,13 @@ module PgHero
496
509
  redirect_to root_path, alert: "Query stats not enabled"
497
510
  end
498
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
499
520
  end
500
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 %>
@@ -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,
@@ -11,7 +11,7 @@ module PgHero
11
11
  end
12
12
 
13
13
  def kill_all
14
- select_all <<-SQL
14
+ select_all <<~SQL
15
15
  SELECT
16
16
  pg_terminate_backend(pid)
17
17
  FROM
@@ -9,7 +9,7 @@ module PgHero
9
9
  max_value = max_value.to_i
10
10
  threshold = threshold.to_i
11
11
 
12
- select_all <<-SQL
12
+ select_all <<~SQL
13
13
  SELECT
14
14
  n.nspname AS schema,
15
15
  c.relname AS table,
@@ -35,7 +35,7 @@ module PgHero
35
35
 
36
36
  def vacuum_progress
37
37
  if server_version_num >= 90600
38
- select_all <<-SQL
38
+ select_all <<~SQL
39
39
  SELECT
40
40
  pid,
41
41
  phase
@@ -50,7 +50,7 @@ module PgHero
50
50
  end
51
51
 
52
52
  def maintenance_info
53
- select_all <<-SQL
53
+ select_all <<~SQL
54
54
  SELECT
55
55
  schemaname AS schema,
56
56
  relname AS table,