pghero 2.8.3 → 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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/LICENSE.txt +1 -1
  4. data/app/assets/javascripts/pghero/Chart.bundle.js +23379 -19766
  5. data/app/assets/javascripts/pghero/application.js +13 -12
  6. data/app/assets/javascripts/pghero/chartkick.js +834 -764
  7. data/app/assets/javascripts/pghero/highlight.min.js +440 -0
  8. data/app/assets/javascripts/pghero/jquery.js +318 -197
  9. data/app/assets/javascripts/pghero/nouislider.js +676 -1066
  10. data/app/assets/stylesheets/pghero/application.css +8 -2
  11. data/app/assets/stylesheets/pghero/nouislider.css +4 -10
  12. data/app/controllers/pg_hero/home_controller.rb +103 -37
  13. data/app/helpers/pg_hero/home_helper.rb +2 -2
  14. data/app/views/layouts/pg_hero/application.html.erb +4 -2
  15. data/app/views/pg_hero/home/_query_stats_slider.html.erb +6 -6
  16. data/app/views/pg_hero/home/connections.html.erb +6 -6
  17. data/app/views/pg_hero/home/explain.html.erb +4 -2
  18. data/app/views/pg_hero/home/index.html.erb +3 -1
  19. data/app/views/pg_hero/home/queries.html.erb +4 -2
  20. data/app/views/pg_hero/home/relation_space.html.erb +1 -1
  21. data/app/views/pg_hero/home/show_query.html.erb +17 -13
  22. data/app/views/pg_hero/home/space.html.erb +44 -40
  23. data/app/views/pg_hero/home/system.html.erb +6 -6
  24. data/lib/generators/pghero/query_stats_generator.rb +1 -0
  25. data/lib/generators/pghero/space_stats_generator.rb +1 -0
  26. data/lib/generators/pghero/templates/config.yml.tt +6 -0
  27. data/lib/pghero/database.rb +0 -7
  28. data/lib/pghero/engine.rb +1 -1
  29. data/lib/pghero/methods/basic.rb +6 -9
  30. data/lib/pghero/methods/connections.rb +5 -5
  31. data/lib/pghero/methods/constraints.rb +1 -1
  32. data/lib/pghero/methods/explain.rb +34 -0
  33. data/lib/pghero/methods/indexes.rb +8 -8
  34. data/lib/pghero/methods/kill.rb +1 -1
  35. data/lib/pghero/methods/maintenance.rb +4 -4
  36. data/lib/pghero/methods/queries.rb +2 -2
  37. data/lib/pghero/methods/query_stats.rb +28 -29
  38. data/lib/pghero/methods/replication.rb +2 -2
  39. data/lib/pghero/methods/sequences.rb +3 -3
  40. data/lib/pghero/methods/settings.rb +1 -1
  41. data/lib/pghero/methods/space.rb +20 -14
  42. data/lib/pghero/methods/suggested_indexes.rb +40 -110
  43. data/lib/pghero/methods/system.rb +16 -16
  44. data/lib/pghero/methods/tables.rb +4 -5
  45. data/lib/pghero/methods/users.rb +12 -4
  46. data/lib/pghero/version.rb +1 -1
  47. data/lib/pghero.rb +90 -65
  48. data/lib/tasks/pghero.rake +11 -1
  49. data/licenses/LICENSE-chart.js.txt +1 -1
  50. data/licenses/LICENSE-date-fns.txt +21 -20
  51. data/licenses/LICENSE-kurkle-color.txt +9 -0
  52. metadata +9 -8
  53. 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,11 +87,11 @@ 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)
87
- @growth_bytes_by_relation = Hash[ space_growth.map { |r| [[r[:schema], r[:relation]], r[:growth_bytes]] } ]
94
+ @growth_bytes_by_relation = space_growth.to_h { |r| [[r[:schema], r[:relation]], r[:growth_bytes]] }
88
95
  if params[:sort] == "growth"
89
96
  @relation_sizes.sort_by! { |r| s = @growth_bytes_by_relation[[r[:schema], r[:relation]]]; [s ? 0 : 1, -s.to_i, r[:schema], r[:relation]] }
90
97
  end
@@ -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"
@@ -184,7 +192,7 @@ module PgHero
184
192
  @chart2_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at].change(sec: 0), r[:average_time].round(1)] }, library: chart_library_options}]
185
193
  @chart3_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at].change(sec: 0), r[:calls]] }, library: chart_library_options}]
186
194
 
187
- @origins = Hash[query_hash_stats.group_by { |r| r[:origin].to_s }.map { |k, v| [k, v.size] }]
195
+ @origins = query_hash_stats.group_by { |r| r[:origin].to_s }.to_h { |k, v| [k, v.size] }
188
196
  @total_count = query_hash_stats.size
189
197
  end
190
198
 
@@ -192,11 +200,12 @@ module PgHero
192
200
  @tables.sort!
193
201
 
194
202
  if @tables.any?
195
- @row_counts = Hash[@database.table_stats(table: @tables).map { |i| [i[:table], i[:estimated_rows]] }]
196
- @indexes_by_table = @database.indexes.group_by { |i| i[:table] }
203
+ @row_counts = @database.table_stats(table: @tables).to_h { |i| [i[:table], i[:estimated_rows]] }
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
- render_text "Unknown query"
208
+ render_text "Unknown query", status: :not_found
200
209
  end
201
210
  end
202
211
 
@@ -217,9 +226,9 @@ module PgHero
217
226
  @period = (params[:period] || 60.seconds).to_i
218
227
 
219
228
  if @duration / @period > 1440
220
- render_text "Too many data points"
229
+ render_text "Too many data points", status: :bad_request
221
230
  elsif @period % 60 != 0
222
- render_text "Period must be a multiple of 60"
231
+ render_text "Period must be a multiple of 60", status: :bad_request
223
232
  end
224
233
  end
225
234
 
@@ -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},
@@ -263,30 +279,57 @@ module PgHero
263
279
  end
264
280
 
265
281
  def explain
282
+ unless @explain_enabled
283
+ render_text "Explain not enabled", status: :bad_request
284
+ return
285
+ end
286
+
266
287
  @title = "Explain"
267
288
  @query = params[:query]
289
+ @explain_analyze_enabled = PgHero.explain_mode == "analyze"
290
+
268
291
  # TODO use get + token instead of post so users can share links
269
292
  # need to prevent CSRF and DoS
270
- if request.post? && @query
293
+ if request.post? && @query.present?
271
294
  begin
272
- prefix =
295
+ explain_options =
273
296
  case params[:commit]
274
297
  when "Analyze"
275
- "ANALYZE "
298
+ {analyze: true}
276
299
  when "Visualize"
277
- "(ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) "
300
+ if @explain_analyze_enabled
301
+ {analyze: true, costs: true, verbose: true, buffers: true, format: "json"}
302
+ else
303
+ {costs: true, verbose: true, format: "json"}
304
+ end
278
305
  else
279
- ""
306
+ {}
280
307
  end
281
- @explanation = @database.explain("#{prefix}#{@query}")
308
+
309
+ if explain_options[:analyze] && !@explain_analyze_enabled
310
+ render_text "Explain analyze not enabled", status: :bad_request
311
+ return
312
+ end
313
+
314
+ @explanation = @database.explain_v2(@query, **explain_options)
282
315
  @suggested_index = @database.suggested_indexes(queries: [@query]).first if @database.suggested_indexes_enabled?
283
316
  @visualize = params[:commit] == "Visualize"
284
317
  rescue ActiveRecord::StatementInvalid => e
285
- @error = e.message
286
-
287
- if @error.include?("bind message supplies 0 parameters")
288
- @error = "Can't explain queries with bind parameters"
289
- end
318
+ message = e.message
319
+ @error =
320
+ if message == "Unsafe statement"
321
+ "Unsafe statement"
322
+ elsif message.start_with?("PG::ProtocolViolation: ERROR: bind message supplies 0 parameters")
323
+ "Can't explain queries with bind parameters"
324
+ elsif message.start_with?("PG::SyntaxError")
325
+ "Syntax error with query"
326
+ elsif message.start_with?("PG::QueryCanceled")
327
+ "Query timed out"
328
+ else
329
+ # default to a generic message
330
+ # since data can be extracted through the Postgres error message
331
+ "Error explaining query"
332
+ end
290
333
  end
291
334
  end
292
335
  end
@@ -368,11 +411,20 @@ module PgHero
368
411
  redirect_backward alert: "The database user does not have permission to enable query stats"
369
412
  end
370
413
 
414
+ # TODO disable if historical query stats enabled?
371
415
  def reset_query_stats
372
- @database.reset_query_stats
373
- redirect_backward notice: "Query stats reset"
374
- rescue ActiveRecord::StatementInvalid
375
- redirect_backward alert: "The database user does not have permission to reset query stats"
416
+ success =
417
+ if @database.server_version_num >= 120000
418
+ @database.reset_query_stats
419
+ else
420
+ @database.reset_instance_query_stats
421
+ end
422
+
423
+ if success
424
+ redirect_backward notice: "Query stats reset"
425
+ else
426
+ redirect_backward alert: "The database user does not have permission to reset query stats"
427
+ end
376
428
  end
377
429
 
378
430
  protected
@@ -401,17 +453,22 @@ module PgHero
401
453
  @query_stats_enabled = @database.query_stats_enabled?
402
454
  @system_stats_enabled = @database.system_stats_enabled?
403
455
  @replica = @database.replica?
456
+ @explain_enabled = PgHero.explain_enabled?
404
457
  end
405
458
 
406
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
+
407
464
  @suggested_indexes_by_query =
408
- if @database.suggested_indexes_enabled?
409
- @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)
410
467
  else
411
468
  {}
412
469
  end
413
470
 
414
- @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)
415
472
  @query_stats_by_query = @query_stats.index_by { |q| q[:query] }
416
473
  @debug = params[:debug].present?
417
474
  end
@@ -445,12 +502,13 @@ module PgHero
445
502
  end
446
503
 
447
504
  def check_api
448
- render_text "No support for Rails API. See https://github.com/pghero/pghero for a standalone app." if Rails.application.config.try(:api_only)
505
+ if Rails.application.config.try(:api_only)
506
+ render_text "No support for Rails API. See https://github.com/pghero/pghero for a standalone app.", status: :internal_server_error
507
+ end
449
508
  end
450
509
 
451
- # TODO return error status code
452
- def render_text(message)
453
- render plain: message
510
+ def render_text(message, status:)
511
+ render plain: message, status: status
454
512
  end
455
513
 
456
514
  def ensure_query_stats
@@ -458,5 +516,13 @@ module PgHero
458
516
  redirect_to root_path, alert: "Query stats not enabled"
459
517
  end
460
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
461
527
  end
462
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" %>
@@ -48,7 +48,9 @@
48
48
  <% unless @database.replica? %>
49
49
  <li class="<%= controller.action_name == "maintenance" ? "active" : "" %>"><%= link_to "Maintenance", maintenance_path %></li>
50
50
  <% end %>
51
- <li class="<%= controller.action_name == "explain" ? "active" : "" %>"><%= link_to "Explain", explain_path %></li>
51
+ <% if @explain_enabled %>
52
+ <li class="<%= controller.action_name == "explain" ? "active" : "" %>"><%= link_to "Explain", explain_path %></li>
53
+ <% end %>
52
54
  <li class="<%= controller.action_name == "tune" ? "active" : "" %>"><%= link_to "Tune", tune_path %></li>
53
55
  </ul>
54
56
 
@@ -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
 
@@ -5,14 +5,16 @@
5
5
  <div class="field"><%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %></div>
6
6
  <p>
7
7
  <%= submit_tag "Explain", class: "btn btn-info", style: "margin-right: 10px;" %>
8
- <%= submit_tag "Analyze", class: "btn btn-danger", style: "margin-right: 10px;" %>
8
+ <% if @explain_analyze_enabled %>
9
+ <%= submit_tag "Analyze", class: "btn btn-danger", style: "margin-right: 10px;" %>
10
+ <% end %>
9
11
  <%= submit_tag "Visualize", class: "btn btn-danger" %>
10
12
  </p>
11
13
  <% end %>
12
14
 
13
15
  <% if @explanation %>
14
16
  <% if @visualize %>
15
- <p>Paste the output below into the <%= link_to "Postgres Explain Visualizer", "https://tatiyants.com/pev/#/plans/new", target: "_blank" %></p>
17
+ <p>Paste the output below into the <%= link_to "explain visualizer", PgHero.visualize_url, target: "_blank" %></p>
16
18
  <% end %>
17
19
  <pre><code><%= @explanation %></code></pre>
18
20
  <% unless @visualize %>
@@ -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>
@@ -4,7 +4,7 @@
4
4
  highlightQueries()
5
5
  </script>
6
6
 
7
- <% if @explainable_query %>
7
+ <% if @explain_enabled && @explainable_query %>
8
8
  <p>
9
9
  <%= button_to "Explain", explain_path, params: {query: @explainable_query}, form: {target: "_blank"}, class: "btn btn-info" %>
10
10
  </p>
@@ -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>