pghero 2.8.3 → 3.3.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -24,9 +24,15 @@ databases:
24
24
  # Minimum connections for high connections warning
25
25
  # total_connections_threshold: 500
26
26
 
27
+ # Explain functionality
28
+ # explain: true / false / analyze
29
+
27
30
  # Statement timeout for explain
28
31
  # explain_timeout_sec: 10
29
32
 
33
+ # Visualize URL for explain
34
+ # visualize_url: https://...
35
+
30
36
  # Time zone (defaults to app time zone)
31
37
  # time_zone: "Pacific Time (US & Canada)"
32
38
 
@@ -118,12 +118,6 @@ module PgHero
118
118
  @filter_data
119
119
  end
120
120
 
121
- # TODO remove in next major version
122
- alias_method :access_key_id, :aws_access_key_id
123
- alias_method :secret_access_key, :aws_secret_access_key
124
- alias_method :region, :aws_region
125
- alias_method :db_instance_identifier, :aws_db_instance_identifier
126
-
127
121
  private
128
122
 
129
123
  # check adapter lazily
@@ -148,7 +142,6 @@ module PgHero
148
142
 
149
143
  # resolve spec
150
144
  if !url && config["spec"]
151
- raise Error, "Spec requires Rails 6+" unless PgHero.spec_supported?
152
145
  config_options = {env_name: PgHero.env, PgHero.spec_name_key => config["spec"], PgHero.include_replicas_key => true}
153
146
  resolved = ActiveRecord::Base.configurations.configs_for(**config_options)
154
147
  raise Error, "Spec not found: #{config["spec"]}" unless resolved
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"
@@ -45,7 +45,7 @@ module PgHero
45
45
  if ActiveRecord::VERSION::STRING.to_f >= 6.1
46
46
  result = result.map(&:symbolize_keys)
47
47
  else
48
- result = result.map { |row| Hash[row.map { |col, val| [col.to_sym, result.column_types[col].send(:cast_value, val)] }] }
48
+ result = result.map { |row| row.to_h { |col, val| [col.to_sym, result.column_types[col].send(:cast_value, val)] } }
49
49
  end
50
50
  if filter_data
51
51
  query_columns.each do |column|
@@ -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
@@ -53,11 +53,11 @@ module PgHero
53
53
  2 DESC, 1
54
54
  SQL
55
55
 
56
- Hash[states.map { |s| [s[:state], s[:connections]] }]
56
+ states.to_h { |s| [s[:state], s[:connections]] }
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,
@@ -1,6 +1,8 @@
1
1
  module PgHero
2
2
  module Methods
3
3
  module Explain
4
+ # TODO remove in 4.0
5
+ # note: this method is not affected by the explain option
4
6
  def explain(sql)
5
7
  sql = squish(sql)
6
8
  explanation = nil
@@ -16,6 +18,23 @@ module PgHero
16
18
  explanation
17
19
  end
18
20
 
21
+ # TODO rename to explain in 4.0
22
+ # note: this method is not affected by the explain option
23
+ def explain_v2(sql, analyze: nil, verbose: nil, costs: nil, settings: nil, buffers: nil, wal: nil, timing: nil, summary: nil, format: "text")
24
+ options = []
25
+ add_explain_option(options, "ANALYZE", analyze)
26
+ add_explain_option(options, "VERBOSE", verbose)
27
+ add_explain_option(options, "SETTINGS", settings)
28
+ add_explain_option(options, "COSTS", costs)
29
+ add_explain_option(options, "BUFFERS", buffers)
30
+ add_explain_option(options, "WAL", wal)
31
+ add_explain_option(options, "TIMING", timing)
32
+ add_explain_option(options, "SUMMARY", summary)
33
+ options << "FORMAT #{explain_format(format)}"
34
+
35
+ explain("(#{options.join(", ")}) #{sql}")
36
+ end
37
+
19
38
  private
20
39
 
21
40
  def explain_safe?
@@ -24,6 +43,21 @@ module PgHero
24
43
  rescue ActiveRecord::StatementInvalid
25
44
  true
26
45
  end
46
+
47
+ def add_explain_option(options, name, value)
48
+ unless value.nil?
49
+ options << "#{name}#{value ? "" : " FALSE"}"
50
+ end
51
+ end
52
+
53
+ # important! validate format to prevent injection
54
+ def explain_format(format)
55
+ if ["text", "xml", "json", "yaml"].include?(format)
56
+ format.upcase
57
+ else
58
+ raise ArgumentError, "Unknown format"
59
+ end
60
+ end
27
61
  end
28
62
  end
29
63
  end
@@ -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
@@ -1,7 +1,7 @@
1
1
  module PgHero
2
2
  module Methods
3
3
  module Maintenance
4
- # https://www.postgresql.org/docs/9.1/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
4
+ # https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
5
5
  # "the system will shut down and refuse to start any new transactions
6
6
  # once there are fewer than 1 million transactions left until wraparound"
7
7
  # warn when 10,000,000 transactions left
@@ -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,
@@ -2,7 +2,7 @@ module PgHero
2
2
  module Methods
3
3
  module Queries
4
4
  def running_queries(min_duration: nil, all: false)
5
- query = <<-SQL
5
+ query = <<~SQL
6
6
  SELECT
7
7
  pid,
8
8
  state,
@@ -36,7 +36,7 @@ module PgHero
36
36
  # from https://wiki.postgresql.org/wiki/Lock_Monitoring
37
37
  # and https://big-elephants.com/2013-09/exploring-query-locks-in-postgres/
38
38
  def blocked_queries
39
- query = <<-SQL
39
+ query = <<~SQL
40
40
  SELECT
41
41
  COALESCE(blockingl.relation::regclass::text,blockingl.locktype) as locked_item,
42
42
  blockeda.pid AS blocked_pid,
@@ -56,10 +56,9 @@ module PgHero
56
56
  true
57
57
  end
58
58
 
59
- # TODO scope by database in PgHero 3.0
60
- # (add database: database_name to options)
61
59
  def reset_query_stats(**options)
62
- reset_instance_query_stats(**options)
60
+ raise PgHero::Error, "Use reset_instance_query_stats to pass database" if options.delete(:database)
61
+ reset_instance_query_stats(**options, database: database_name)
63
62
  end
64
63
 
65
64
  # resets query stats for the entire instance
@@ -121,7 +120,7 @@ module PgHero
121
120
  server_version_num >= 90400
122
121
  end
123
122
 
124
- # resetting query stats will reset across the entire Postgres instance
123
+ # resetting query stats will reset across the entire Postgres instance in Postgres < 12
125
124
  # this is problematic if multiple PgHero databases use the same Postgres instance
126
125
  #
127
126
  # to get around this, we capture queries for every Postgres database before we
@@ -147,16 +146,15 @@ module PgHero
147
146
  # nothing to do
148
147
  return if query_stats.empty?
149
148
 
150
- # use mapping, not query stats here
151
- # TODO add option for this, and make default in PgHero 3.0
152
- if false # mapping.size == 1 && server_version_num >= 120000
149
+ # reset individual databases for Postgres 12+ instance
150
+ if server_version_num >= 120000
153
151
  query_stats.each do |db_id, db_query_stats|
154
- if reset_query_stats(database: mapping[db_id], raise_errors: raise_errors)
152
+ if reset_instance_query_stats(database: mapping[db_id], raise_errors: raise_errors)
155
153
  insert_query_stats(db_id, db_query_stats, now)
156
154
  end
157
155
  end
158
156
  else
159
- if reset_query_stats(raise_errors: raise_errors)
157
+ if reset_instance_query_stats(raise_errors: raise_errors)
160
158
  query_stats.each do |db_id, db_query_stats|
161
159
  insert_query_stats(db_id, db_query_stats, now)
162
160
  end
@@ -164,8 +162,9 @@ module PgHero
164
162
  end
165
163
  end
166
164
 
167
- def clean_query_stats
168
- PgHero::QueryStats.where(database: id).where("captured_at < ?", 14.days.ago).delete_all
165
+ def clean_query_stats(before: nil)
166
+ before ||= 14.days.ago
167
+ PgHero::QueryStats.where(database: id).where("captured_at < ?", before).delete_all
169
168
  end
170
169
 
171
170
  def slow_queries(query_stats: nil, **options)
@@ -173,10 +172,11 @@ module PgHero
173
172
  query_stats.select { |q| q[:calls].to_i >= slow_query_calls.to_i && q[:average_time].to_f >= slow_query_ms.to_f }
174
173
  end
175
174
 
175
+ # TODO option to include current period
176
176
  def query_hash_stats(query_hash, user: nil)
177
177
  if historical_query_stats_enabled? && supports_query_hash?
178
178
  start_at = 24.hours.ago
179
- select_all_stats <<-SQL
179
+ select_all_stats <<~SQL
180
180
  SELECT
181
181
  captured_at,
182
182
  total_time / 1000 / 60 AS total_minutes,
@@ -206,7 +206,7 @@ module PgHero
206
206
  limit ||= 100
207
207
  sort ||= "total_minutes"
208
208
  total_time = server_version_num >= 130000 ? "(total_plan_time + total_exec_time)" : "total_time"
209
- query = <<-SQL
209
+ query = <<~SQL
210
210
  WITH query_stats AS (
211
211
  SELECT
212
212
  LEFT(query, 10000) AS query,
@@ -228,6 +228,7 @@ module PgHero
228
228
  )
229
229
  SELECT
230
230
  query,
231
+ query AS explainable_query,
231
232
  query_hash,
232
233
  query_stats.user,
233
234
  total_minutes,
@@ -238,14 +239,14 @@ module PgHero
238
239
  FROM
239
240
  query_stats
240
241
  ORDER BY
241
- #{quote_table_name(sort)} DESC
242
+ #{quote_column_name(sort)} DESC
242
243
  LIMIT #{limit.to_i}
243
244
  SQL
244
245
 
245
246
  # we may be able to skip query_columns
246
247
  # in more recent versions of Postgres
247
248
  # as pg_stat_statements should be already normalized
248
- select_all(query, query_columns: [:query])
249
+ select_all(query, query_columns: [:query, :explainable_query])
249
250
  else
250
251
  raise NotEnabled, "Query stats not enabled"
251
252
  end
@@ -254,7 +255,7 @@ module PgHero
254
255
  def historical_query_stats(sort: nil, start_at: nil, end_at: nil, query_hash: nil)
255
256
  if historical_query_stats_enabled?
256
257
  sort ||= "total_minutes"
257
- query = <<-SQL
258
+ query = <<~SQL
258
259
  WITH query_stats AS (
259
260
  SELECT
260
261
  #{supports_query_hash? ? "query_hash" : "md5(query)"} AS query_hash,
@@ -287,7 +288,7 @@ module PgHero
287
288
  FROM
288
289
  query_stats
289
290
  ORDER BY
290
- #{quote_table_name(sort)} DESC
291
+ #{quote_column_name(sort)} DESC
291
292
  LIMIT 100
292
293
  SQL
293
294
 
@@ -330,19 +331,17 @@ module PgHero
330
331
  def insert_query_stats(db_id, db_query_stats, now)
331
332
  values =
332
333
  db_query_stats.map do |qs|
333
- [
334
- db_id,
335
- qs[:query],
336
- qs[:total_minutes] * 60 * 1000,
337
- qs[:calls],
338
- now,
339
- supports_query_hash? ? qs[:query_hash] : nil,
340
- qs[:user]
341
- ]
334
+ {
335
+ database: db_id,
336
+ query: qs[:query],
337
+ total_time: qs[:total_minutes] * 60 * 1000,
338
+ calls: qs[:calls],
339
+ captured_at: now,
340
+ query_hash: supports_query_hash? ? qs[:query_hash] : nil,
341
+ user: qs[:user]
342
+ }
342
343
  end
343
-
344
- columns = %w[database query total_time calls captured_at query_hash user]
345
- insert_stats("pghero_query_stats", columns, values)
344
+ PgHero::QueryStats.insert_all!(values)
346
345
  end
347
346
  end
348
347
  end
@@ -18,7 +18,7 @@ module PgHero
18
18
  "pg_last_xlog_receive_location() = pg_last_xlog_replay_location()"
19
19
  end
20
20
 
21
- select_one <<-SQL
21
+ select_one <<~SQL
22
22
  SELECT
23
23
  CASE
24
24
  WHEN NOT pg_is_in_recovery() OR #{lag_condition} THEN 0
@@ -32,7 +32,7 @@ module PgHero
32
32
  def replication_slots
33
33
  if server_version_num >= 90400
34
34
  with_feature_support(:replication_slots, []) do
35
- select_all <<-SQL
35
+ select_all <<~SQL
36
36
  SELECT
37
37
  slot_name,
38
38
  database,
@@ -7,7 +7,7 @@ module PgHero
7
7
  # it's what information_schema.columns uses
8
8
  # also, exclude temporary tables to prevent error
9
9
  # when accessing across sessions
10
- sequences = select_all <<-SQL
10
+ sequences = select_all <<~SQL
11
11
  SELECT
12
12
  n.nspname AS table_schema,
13
13
  c.relname AS table,
@@ -84,7 +84,7 @@ module PgHero
84
84
  # also adds schema if missing
85
85
  def add_sequence_attributes(sequences)
86
86
  # fetch data
87
- sequence_attributes = select_all <<-SQL
87
+ sequence_attributes = select_all <<~SQL
88
88
  SELECT
89
89
  n.nspname AS schema,
90
90
  c.relname AS sequence,
@@ -115,7 +115,7 @@ module PgHero
115
115
  end
116
116
 
117
117
  # then populate attributes
118
- readable = Hash[sequence_attributes.map { |s| [[s[:schema], s[:sequence]], s[:readable]] }]
118
+ readable = sequence_attributes.to_h { |s| [[s[:schema], s[:sequence]], s[:readable]] }
119
119
  sequences.each do |sequence|
120
120
  sequence[:readable] = readable[[sequence[:schema], sequence[:sequence]]] || false
121
121
  end
@@ -30,7 +30,7 @@ module PgHero
30
30
  private
31
31
 
32
32
  def fetch_settings(names)
33
- Hash[names.map { |name| [name, select_one("SHOW #{name}")] }]
33
+ names.to_h { |name| [name, select_one("SHOW #{name}")] }
34
34
  end
35
35
  end
36
36
  end
@@ -6,7 +6,7 @@ module PgHero
6
6
  end
7
7
 
8
8
  def relation_sizes
9
- select_all_size <<-SQL
9
+ select_all_size <<~SQL
10
10
  SELECT
11
11
  n.nspname AS schema,
12
12
  c.relname AS relation,
@@ -27,7 +27,7 @@ module PgHero
27
27
  end
28
28
 
29
29
  def table_sizes
30
- select_all_size <<-SQL
30
+ select_all_size <<~SQL
31
31
  SELECT
32
32
  n.nspname AS schema,
33
33
  c.relname AS table,
@@ -49,10 +49,10 @@ module PgHero
49
49
  def space_growth(days: 7, relation_sizes: nil)
50
50
  if space_stats_enabled?
51
51
  relation_sizes ||= self.relation_sizes
52
- sizes = Hash[ relation_sizes.map { |r| [[r[:schema], r[:relation]], r[:size_bytes]] } ]
52
+ sizes = relation_sizes.to_h { |r| [[r[:schema], r[:relation]], r[:size_bytes]] }
53
53
  start_at = days.days.ago
54
54
 
55
- stats = select_all_stats <<-SQL
55
+ stats = select_all_stats <<~SQL
56
56
  WITH t AS (
57
57
  SELECT
58
58
  schema,
@@ -92,10 +92,10 @@ module PgHero
92
92
  def relation_space_stats(relation, schema: "public")
93
93
  if space_stats_enabled?
94
94
  relation_sizes ||= self.relation_sizes
95
- sizes = Hash[ relation_sizes.map { |r| [[r[:schema], r[:relation]], r[:size_bytes]] } ]
95
+ sizes = relation_sizes.map { |r| [[r[:schema], r[:relation]], r[:size_bytes]] }.to_h
96
96
  start_at = 30.days.ago
97
97
 
98
- stats = select_all_stats <<-SQL
98
+ stats = select_all_stats <<~SQL
99
99
  SELECT
100
100
  captured_at,
101
101
  size AS size_bytes
@@ -121,16 +121,22 @@ module PgHero
121
121
 
122
122
  def capture_space_stats
123
123
  now = Time.now
124
- columns = %w(database schema relation size captured_at)
125
- values = []
126
- relation_sizes.each do |rs|
127
- values << [id, rs[:schema], rs[:relation], rs[:size_bytes].to_i, now]
128
- end
129
- insert_stats("pghero_space_stats", columns, values) if values.any?
124
+ values =
125
+ relation_sizes.map do |rs|
126
+ {
127
+ database: id,
128
+ schema: rs[:schema],
129
+ relation: rs[:relation],
130
+ size: rs[:size_bytes].to_i,
131
+ captured_at: now
132
+ }
133
+ end
134
+ PgHero::SpaceStats.insert_all!(values) if values.any?
130
135
  end
131
136
 
132
- def clean_space_stats
133
- PgHero::SpaceStats.where(database: id).where("captured_at < ?", 90.days.ago).delete_all
137
+ def clean_space_stats(before: nil)
138
+ before ||= 90.days.ago
139
+ PgHero::SpaceStats.where(database: id).where("captured_at < ?", before).delete_all
134
140
  end
135
141
 
136
142
  def space_stats_enabled?