pg_reports 0.5.3 → 0.6.0

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/README.md +12 -4
  4. data/app/controllers/pg_reports/dashboard_controller.rb +6 -2
  5. data/app/views/layouts/pg_reports/application.html.erb +70 -61
  6. data/app/views/pg_reports/dashboard/_show_scripts.html.erb +53 -1
  7. data/app/views/pg_reports/dashboard/_show_styles.html.erb +31 -11
  8. data/app/views/pg_reports/dashboard/index.html.erb +80 -9
  9. data/app/views/pg_reports/dashboard/show.html.erb +6 -2
  10. data/config/locales/en.yml +109 -0
  11. data/config/locales/ru.yml +81 -0
  12. data/config/locales/uk.yml +126 -0
  13. data/lib/pg_reports/compatibility.rb +63 -0
  14. data/lib/pg_reports/configuration.rb +2 -0
  15. data/lib/pg_reports/dashboard/reports_registry.rb +36 -0
  16. data/lib/pg_reports/definitions/indexes/fk_without_indexes.yml +30 -0
  17. data/lib/pg_reports/definitions/indexes/index_correlation.yml +31 -0
  18. data/lib/pg_reports/definitions/indexes/inefficient_indexes.yml +45 -0
  19. data/lib/pg_reports/definitions/queries/temp_file_queries.yml +39 -0
  20. data/lib/pg_reports/definitions/system/wraparound_risk.yml +31 -0
  21. data/lib/pg_reports/definitions/tables/tables_without_pk.yml +28 -0
  22. data/lib/pg_reports/engine.rb +6 -0
  23. data/lib/pg_reports/modules/indexes.rb +3 -0
  24. data/lib/pg_reports/modules/queries.rb +1 -0
  25. data/lib/pg_reports/modules/system.rb +27 -0
  26. data/lib/pg_reports/modules/tables.rb +1 -0
  27. data/lib/pg_reports/query_monitor.rb +139 -42
  28. data/lib/pg_reports/sql/indexes/fk_without_indexes.sql +23 -0
  29. data/lib/pg_reports/sql/indexes/index_correlation.sql +27 -0
  30. data/lib/pg_reports/sql/indexes/inefficient_indexes.sql +22 -0
  31. data/lib/pg_reports/sql/queries/temp_file_queries.sql +16 -0
  32. data/lib/pg_reports/sql/system/checkpoint_stats.sql +20 -0
  33. data/lib/pg_reports/sql/system/checkpoint_stats_legacy.sql +19 -0
  34. data/lib/pg_reports/sql/system/wraparound_risk.sql +21 -0
  35. data/lib/pg_reports/sql/tables/tables_without_pk.sql +20 -0
  36. data/lib/pg_reports/version.rb +1 -1
  37. data/lib/pg_reports.rb +5 -0
  38. metadata +16 -1
@@ -14,6 +14,7 @@ module PgReports
14
14
  # - row_counts(limit: 50)
15
15
  # - cache_hit_ratios(limit: 50)
16
16
  # - seq_scans(limit: 20)
17
+ # - tables_without_pk(limit: 50)
17
18
  # - recently_modified(limit: 20)
18
19
 
19
20
  private
@@ -8,65 +8,99 @@ module PgReports
8
8
  class QueryMonitor
9
9
  include Singleton
10
10
 
11
- attr_reader :enabled, :session_id
11
+ CACHE_KEY_ENABLED = "pg_reports:query_monitor:enabled"
12
+ CACHE_KEY_SESSION_ID = "pg_reports:query_monitor:session_id"
13
+ CACHE_TTL = 24.hours
12
14
 
13
15
  def initialize
14
- @enabled = false
15
16
  @subscriber = nil
16
17
  @mutex = Mutex.new
17
- @session_id = nil
18
18
  @queries = []
19
+ @handling_event = false
20
+
21
+ # Local state — used by the event handler to avoid cache reads
22
+ # (which generate SQL events and cause infinite recursion with DB-backed caches)
23
+ @enabled = false
24
+ @session_id = nil
25
+
26
+ sync_from_cache
27
+ ensure_subscription_if_enabled
28
+ end
29
+
30
+ def enabled
31
+ @enabled
32
+ end
33
+
34
+ def session_id
35
+ @session_id
19
36
  end
20
37
 
21
38
  def start
22
39
  @mutex.synchronize do
23
- if @enabled
40
+ if enabled
41
+ Rails.logger.info("PgReports: Monitoring already active, session_id=#{session_id}") if defined?(Rails)
24
42
  return {success: false, message: "Monitoring already active"}
25
43
  end
26
44
 
27
- @session_id = SecureRandom.uuid
45
+ new_session_id = SecureRandom.uuid
28
46
  @queries = []
47
+
48
+ # Update local state first (used by event handler — no cache round-trip)
29
49
  @enabled = true
50
+ @session_id = new_session_id
30
51
 
31
- # Subscribe to sql.active_record events
32
- @subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |name, started, finished, unique_id, payload|
33
- handle_sql_event(name, started, finished, unique_id, payload)
34
- end
52
+ # Store state in cache so other processes can see it
53
+ cache_write(CACHE_KEY_ENABLED, true)
54
+ cache_write(CACHE_KEY_SESSION_ID, new_session_id)
55
+
56
+ Rails.logger.info("PgReports: Monitoring started, session_id=#{new_session_id}") if defined?(Rails)
57
+
58
+ # Subscribe to sql.active_record events in THIS process
59
+ ensure_subscription
35
60
 
36
61
  # Write session start marker to file
37
62
  write_session_marker("session_start")
38
63
 
39
- {success: true, message: "Query monitoring started", session_id: @session_id}
64
+ {success: true, message: "Query monitoring started", session_id: new_session_id}
40
65
  end
41
66
  rescue => e
42
67
  @enabled = false
68
+ @session_id = nil
69
+ cache_write(CACHE_KEY_ENABLED, false)
43
70
  {success: false, error: e.message}
44
71
  end
45
72
 
46
73
  def stop
47
74
  @mutex.synchronize do
48
- unless @enabled
75
+ unless enabled
49
76
  return {success: false, message: "Monitoring not active"}
50
77
  end
51
78
 
52
- # Unsubscribe from notifications
79
+ current_session_id = @session_id
80
+
81
+ # Clear local state immediately — stops event handler from processing
82
+ @enabled = false
83
+ @session_id = nil
84
+
85
+ # Unsubscribe from notifications in THIS process
53
86
  if @subscriber
54
87
  ActiveSupport::Notifications.unsubscribe(@subscriber)
55
88
  @subscriber = nil
56
89
  end
57
90
 
58
91
  # Write session end marker to file
59
- write_session_marker("session_end")
92
+ write_session_marker("session_end", current_session_id)
60
93
 
61
94
  # Flush queries to file
62
95
  flush_to_file
63
96
 
64
- @enabled = false
97
+ # Clear state from cache so other processes see it
98
+ cache_delete(CACHE_KEY_ENABLED)
99
+ cache_delete(CACHE_KEY_SESSION_ID)
100
+
65
101
  @queries = []
66
- session_id = @session_id
67
- @session_id = nil
68
102
 
69
- {success: true, message: "Query monitoring stopped", session_id: session_id}
103
+ {success: true, message: "Query monitoring stopped", session_id: current_session_id}
70
104
  end
71
105
  rescue => e
72
106
  {success: false, error: e.message}
@@ -74,8 +108,8 @@ module PgReports
74
108
 
75
109
  def status
76
110
  {
77
- enabled: @enabled,
78
- session_id: @session_id,
111
+ enabled: enabled,
112
+ session_id: session_id,
79
113
  query_count: @queries.size
80
114
  }
81
115
  end
@@ -130,31 +164,90 @@ module PgReports
130
164
 
131
165
  private
132
166
 
133
- def handle_sql_event(name, started, finished, unique_id, payload)
167
+ # Cache helpers - work with or without Rails.cache
168
+ def cache_read(key)
169
+ return nil unless cache_available?
170
+ Rails.cache.read(key)
171
+ rescue => e
172
+ Rails.logger.warn("PgReports: Cache read failed: #{e.message}") if defined?(Rails.logger)
173
+ nil
174
+ end
175
+
176
+ def cache_write(key, value)
177
+ return false unless cache_available?
178
+ Rails.cache.write(key, value, expires_in: CACHE_TTL)
179
+ rescue => e
180
+ Rails.logger.warn("PgReports: Cache write failed: #{e.message}") if defined?(Rails.logger)
181
+ false
182
+ end
183
+
184
+ def cache_delete(key)
185
+ return false unless cache_available?
186
+ Rails.cache.delete(key)
187
+ rescue => e
188
+ Rails.logger.warn("PgReports: Cache delete failed: #{e.message}") if defined?(Rails.logger)
189
+ false
190
+ end
191
+
192
+ def cache_available?
193
+ defined?(Rails) && defined?(Rails.cache)
194
+ end
195
+
196
+ # Sync local state from shared cache (called on initialize for multi-process support)
197
+ def sync_from_cache
198
+ @enabled = enabled
199
+ @session_id = session_id
200
+ end
201
+
202
+ # Ensure this process is subscribed to notifications if monitoring is enabled
203
+ def ensure_subscription_if_enabled
134
204
  return unless @enabled
205
+ ensure_subscription
206
+ end
135
207
 
136
- # Skip if should be filtered
137
- return if should_skip?(payload)
208
+ def ensure_subscription
209
+ return if @subscriber # Already subscribed
138
210
 
139
- duration_ms = ((finished - started) * 1000).round(2)
140
- sql = payload[:sql]
141
- query_name = payload[:name]
142
-
143
- # Extract source location
144
- source_location = extract_source_location
145
-
146
- # Build query entry
147
- query_entry = {
148
- type: "query",
149
- session_id: @session_id,
150
- sql: sql,
151
- duration_ms: duration_ms,
152
- name: query_name,
153
- source_location: source_location,
154
- timestamp: Time.current.iso8601
155
- }
211
+ @subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |name, started, finished, unique_id, payload|
212
+ handle_sql_event(name, started, finished, unique_id, payload)
213
+ end
156
214
 
157
- add_to_buffer(query_entry)
215
+ Rails.logger.debug("PgReports: Subscribed to sql.active_record in process #{Process.pid}") if defined?(Rails.logger)
216
+ end
217
+
218
+ def handle_sql_event(name, started, finished, unique_id, payload)
219
+ # Use local @enabled instead of enabled (which hits cache and may generate SQL,
220
+ # causing infinite recursion with database-backed cache stores like SolidCache)
221
+ return unless @enabled
222
+ return if @handling_event
223
+
224
+ @handling_event = true
225
+ begin
226
+ # Skip if should be filtered
227
+ return if should_skip?(payload)
228
+
229
+ duration_ms = ((finished - started) * 1000).round(2)
230
+ sql = payload[:sql]
231
+ query_name = payload[:name]
232
+
233
+ # Extract source location
234
+ source_location = extract_source_location
235
+
236
+ # Build query entry
237
+ query_entry = {
238
+ type: "query",
239
+ session_id: @session_id,
240
+ sql: sql,
241
+ duration_ms: duration_ms,
242
+ name: query_name,
243
+ source_location: source_location,
244
+ timestamp: Time.current.iso8601
245
+ }
246
+
247
+ add_to_buffer(query_entry)
248
+ ensure
249
+ @handling_event = false
250
+ end
158
251
  end
159
252
 
160
253
  def should_skip?(payload)
@@ -197,6 +290,10 @@ module PgReports
197
290
  # Exclude test paths
198
291
  next if path.include?("/spec/")
199
292
 
293
+ # IMPORTANT: Exclude query_monitor.rb itself to prevent false positives
294
+ # when gem is installed from RubyGems
295
+ next if path.include?("/query_monitor.rb")
296
+
200
297
  # Filter queries from pg_reports internal modules only:
201
298
  # - Installed gem: /gems/pg_reports-X.Y.Z/lib/
202
299
  # - Local gem: /pg_reports/lib/pg_reports/modules/
@@ -252,12 +349,12 @@ module PgReports
252
349
  end
253
350
  end
254
351
 
255
- def write_session_marker(marker_type)
352
+ def write_session_marker(marker_type, sid = @session_id)
256
353
  return unless log_file_enabled?
257
354
 
258
355
  marker = {
259
356
  type: marker_type,
260
- session_id: @session_id,
357
+ session_id: sid,
261
358
  timestamp: Time.current.iso8601
262
359
  }
263
360
 
@@ -0,0 +1,23 @@
1
+ -- Foreign keys without indexes on the referencing (child) table
2
+ -- Missing indexes cause sequential scans on DELETE/UPDATE of parent rows
3
+
4
+ SELECT
5
+ c.conname AS constraint_name,
6
+ c.conrelid::regclass::text AS child_table,
7
+ a.attname AS child_column,
8
+ c.confrelid::regclass::text AS parent_table,
9
+ pa.attname AS parent_column,
10
+ pg_size_pretty(pg_relation_size(c.conrelid)) AS child_table_size,
11
+ ROUND(pg_relation_size(c.conrelid) / 1024.0 / 1024.0, 2) AS child_table_size_mb
12
+ FROM pg_constraint c
13
+ JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
14
+ JOIN pg_attribute pa ON pa.attrelid = c.confrelid AND pa.attnum = ANY(c.confkey)
15
+ WHERE c.contype = 'f'
16
+ AND NOT EXISTS (
17
+ SELECT 1
18
+ FROM pg_index i
19
+ WHERE i.indrelid = c.conrelid
20
+ AND a.attnum = ANY(i.indkey)
21
+ AND i.indkey[0] = a.attnum
22
+ )
23
+ ORDER BY pg_relation_size(c.conrelid) DESC;
@@ -0,0 +1,27 @@
1
+ -- Index correlation: how well physical row order matches index order
2
+ -- Low correlation on frequently range-scanned columns means excessive random I/O
3
+
4
+ SELECT
5
+ s.schemaname AS schema,
6
+ s.tablename AS table_name,
7
+ s.attname AS column_name,
8
+ i.indexrelname AS index_name,
9
+ ROUND(s.correlation::numeric, 4) AS correlation,
10
+ ABS(s.correlation) AS abs_correlation,
11
+ s.n_distinct,
12
+ pg_size_pretty(pg_relation_size(c.oid)) AS table_size,
13
+ ROUND(pg_relation_size(c.oid) / 1024.0 / 1024.0, 2) AS table_size_mb,
14
+ si.idx_scan
15
+ FROM pg_stats s
16
+ JOIN pg_class c ON c.relname = s.tablename
17
+ AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = s.schemaname)
18
+ JOIN pg_index idx ON idx.indrelid = c.oid
19
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attname = s.attname
20
+ AND a.attnum = idx.indkey[0]
21
+ JOIN pg_stat_user_indexes i ON i.indexrelid = idx.indexrelid
22
+ JOIN pg_stat_user_indexes si ON si.indexrelid = idx.indexrelid
23
+ WHERE s.schemaname NOT IN ('pg_catalog', 'information_schema')
24
+ AND ABS(s.correlation) < 0.5
25
+ AND pg_relation_size(c.oid) > 10 * 1024 * 1024
26
+ AND si.idx_scan > 100
27
+ ORDER BY ABS(s.correlation) ASC, pg_relation_size(c.oid) DESC;
@@ -0,0 +1,22 @@
1
+ -- Inefficient index scans: indexes that are used but read far more entries than they fetch
2
+ -- A high idx_tup_read / idx_tup_fetch ratio indicates the index column order
3
+ -- does not match query predicates, forcing PostgreSQL to scan large index ranges
4
+ -- Reference: https://www.datadoghq.com/blog/detect-inefficient-index-scans-with-dbm/
5
+
6
+ SELECT
7
+ schemaname AS schema,
8
+ relname AS table_name,
9
+ indexrelname AS index_name,
10
+ idx_scan,
11
+ idx_tup_read,
12
+ idx_tup_fetch,
13
+ ROUND((idx_tup_read::numeric / NULLIF(idx_tup_fetch, 0)), 1) AS read_to_fetch_ratio,
14
+ pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
15
+ ROUND(pg_relation_size(indexrelid) / 1024.0 / 1024.0, 2) AS index_size_mb,
16
+ pg_get_indexdef(indexrelid) AS index_definition
17
+ FROM pg_stat_user_indexes
18
+ WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
19
+ AND idx_scan > 0
20
+ AND idx_tup_fetch > 0
21
+ AND (idx_tup_read::numeric / NULLIF(idx_tup_fetch, 0)) > 10
22
+ ORDER BY (idx_tup_read::numeric / NULLIF(idx_tup_fetch, 0)) DESC;
@@ -0,0 +1,16 @@
1
+ -- Queries that spill to disk via temporary files
2
+ -- High temp file usage indicates insufficient work_mem for these queries
3
+
4
+ SELECT
5
+ queryid,
6
+ LEFT(query, :max_query_length) AS query,
7
+ calls,
8
+ ROUND(temp_blks_written::numeric * 8 / 1024, 2) AS temp_mb_written,
9
+ ROUND(temp_blks_read::numeric * 8 / 1024, 2) AS temp_mb_read,
10
+ ROUND((total_exec_time / 1000)::numeric, 2) AS total_time_sec,
11
+ ROUND((mean_exec_time)::numeric, 2) AS mean_time_ms,
12
+ rows
13
+ FROM pg_stat_statements
14
+ WHERE temp_blks_written > 0
15
+ AND dbid = (SELECT oid FROM pg_database WHERE datname = current_database())
16
+ ORDER BY temp_blks_written DESC;
@@ -0,0 +1,20 @@
1
+ -- Checkpoint and background writer statistics (PostgreSQL 17+)
2
+ -- Uses pg_stat_checkpointer (introduced in PG 17) + pg_stat_bgwriter for bgwriter-only stats
3
+
4
+ SELECT
5
+ pg_stat_get_checkpointer_num_timed() AS checkpoints_timed,
6
+ pg_stat_get_checkpointer_num_requested() AS checkpoints_requested,
7
+ ROUND(pg_stat_get_checkpointer_write_time()::numeric / 1000, 2) AS checkpoint_write_time_sec,
8
+ ROUND(pg_stat_get_checkpointer_sync_time()::numeric / 1000, 2) AS checkpoint_sync_time_sec,
9
+ pg_stat_get_checkpointer_buffers_written() AS buffers_checkpoint,
10
+ pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean,
11
+ pg_stat_get_bgwriter_maxwritten_clean() AS bgwriter_stops,
12
+ pg_stat_get_buf_alloc() AS buffers_alloc,
13
+ CASE
14
+ WHEN (pg_stat_get_checkpointer_num_timed() + pg_stat_get_checkpointer_num_requested()) > 0
15
+ THEN ROUND(
16
+ pg_stat_get_checkpointer_num_requested()::numeric /
17
+ (pg_stat_get_checkpointer_num_timed() + pg_stat_get_checkpointer_num_requested()) * 100, 1)
18
+ ELSE 0
19
+ END AS requested_pct,
20
+ pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
@@ -0,0 +1,19 @@
1
+ -- Checkpoint and background writer statistics (PostgreSQL < 17)
2
+ -- All stats from pg_stat_bgwriter (before checkpoint columns were moved out)
3
+
4
+ SELECT
5
+ checkpoints_timed,
6
+ checkpoints_req AS checkpoints_requested,
7
+ ROUND(checkpoint_write_time::numeric / 1000, 2) AS checkpoint_write_time_sec,
8
+ ROUND(checkpoint_sync_time::numeric / 1000, 2) AS checkpoint_sync_time_sec,
9
+ buffers_checkpoint,
10
+ buffers_clean,
11
+ maxwritten_clean AS bgwriter_stops,
12
+ buffers_alloc,
13
+ CASE
14
+ WHEN (checkpoints_timed + checkpoints_req) > 0
15
+ THEN ROUND(checkpoints_req::numeric / (checkpoints_timed + checkpoints_req) * 100, 1)
16
+ ELSE 0
17
+ END AS requested_pct,
18
+ stats_reset
19
+ FROM pg_stat_bgwriter;
@@ -0,0 +1,21 @@
1
+ -- Transaction ID wraparound risk
2
+ -- When age(datfrozenxid) approaches 2 billion, PostgreSQL will shut down
3
+ -- to prevent data corruption. Monitor this to trigger preventive VACUUM FREEZE.
4
+
5
+ SELECT
6
+ d.datname AS database_name,
7
+ age(d.datfrozenxid) AS xid_age,
8
+ ROUND(age(d.datfrozenxid)::numeric / 2147483647 * 100, 2) AS pct_towards_wraparound,
9
+ 2147483647 - age(d.datfrozenxid) AS remaining_xids,
10
+ current_setting('autovacuum_freeze_max_age')::bigint AS freeze_max_age,
11
+ CASE
12
+ WHEN age(d.datfrozenxid) > current_setting('autovacuum_freeze_max_age')::bigint
13
+ THEN 'CRITICAL - exceeds freeze_max_age'
14
+ WHEN age(d.datfrozenxid) > current_setting('autovacuum_freeze_max_age')::bigint * 0.75
15
+ THEN 'WARNING - approaching freeze_max_age'
16
+ ELSE 'OK'
17
+ END AS status,
18
+ pg_size_pretty(pg_database_size(d.datname)) AS database_size
19
+ FROM pg_database d
20
+ WHERE d.datallowconn = true
21
+ ORDER BY age(d.datfrozenxid) DESC;
@@ -0,0 +1,20 @@
1
+ -- Tables without primary keys
2
+ -- Missing PKs break logical replication and make row identification unreliable
3
+
4
+ SELECT
5
+ n.nspname AS schema,
6
+ c.relname AS table_name,
7
+ c.reltuples::bigint AS estimated_rows,
8
+ pg_size_pretty(pg_relation_size(c.oid)) AS table_size,
9
+ ROUND(pg_relation_size(c.oid) / 1024.0 / 1024.0, 2) AS table_size_mb
10
+ FROM pg_class c
11
+ JOIN pg_namespace n ON n.oid = c.relnamespace
12
+ WHERE c.relkind = 'r'
13
+ AND n.nspname NOT IN ('pg_catalog', 'information_schema')
14
+ AND NOT EXISTS (
15
+ SELECT 1
16
+ FROM pg_index i
17
+ WHERE i.indrelid = c.oid
18
+ AND i.indisprimary
19
+ )
20
+ ORDER BY pg_relation_size(c.oid) DESC;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgReports
4
- VERSION = "0.5.3"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/pg_reports.rb CHANGED
@@ -6,6 +6,7 @@ require "active_record"
6
6
 
7
7
  require_relative "pg_reports/version"
8
8
  require_relative "pg_reports/error"
9
+ require_relative "pg_reports/compatibility"
9
10
  require_relative "pg_reports/configuration"
10
11
  require_relative "pg_reports/sql_loader"
11
12
  require_relative "pg_reports/executor"
@@ -138,3 +139,7 @@ end
138
139
 
139
140
  # Generate YAML-based methods on load
140
141
  PgReports::ModuleGenerator.generate!
142
+
143
+ # Check Ruby and Rails versions immediately (no DB needed)
144
+ PgReports::Compatibility.check_ruby!
145
+ PgReports::Compatibility.check_rails!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_reports
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eldar Avatov
@@ -161,6 +161,7 @@ files:
161
161
  - config/routes.rb
162
162
  - lib/pg_reports.rb
163
163
  - lib/pg_reports/annotation_parser.rb
164
+ - lib/pg_reports/compatibility.rb
164
165
  - lib/pg_reports/configuration.rb
165
166
  - lib/pg_reports/dashboard/reports_registry.rb
166
167
  - lib/pg_reports/definitions/connections/active_connections.yml
@@ -175,8 +176,11 @@ files:
175
176
  - lib/pg_reports/definitions/connections/pool_wait_times.yml
176
177
  - lib/pg_reports/definitions/indexes/bloated_indexes.yml
177
178
  - lib/pg_reports/definitions/indexes/duplicate_indexes.yml
179
+ - lib/pg_reports/definitions/indexes/fk_without_indexes.yml
180
+ - lib/pg_reports/definitions/indexes/index_correlation.yml
178
181
  - lib/pg_reports/definitions/indexes/index_sizes.yml
179
182
  - lib/pg_reports/definitions/indexes/index_usage.yml
183
+ - lib/pg_reports/definitions/indexes/inefficient_indexes.yml
180
184
  - lib/pg_reports/definitions/indexes/invalid_indexes.yml
181
185
  - lib/pg_reports/definitions/indexes/missing_indexes.yml
182
186
  - lib/pg_reports/definitions/indexes/unused_indexes.yml
@@ -186,17 +190,20 @@ files:
186
190
  - lib/pg_reports/definitions/queries/low_cache_hit_queries.yml
187
191
  - lib/pg_reports/definitions/queries/missing_index_queries.yml
188
192
  - lib/pg_reports/definitions/queries/slow_queries.yml
193
+ - lib/pg_reports/definitions/queries/temp_file_queries.yml
189
194
  - lib/pg_reports/definitions/system/activity_overview.yml
190
195
  - lib/pg_reports/definitions/system/cache_stats.yml
191
196
  - lib/pg_reports/definitions/system/database_sizes.yml
192
197
  - lib/pg_reports/definitions/system/extensions.yml
193
198
  - lib/pg_reports/definitions/system/settings.yml
199
+ - lib/pg_reports/definitions/system/wraparound_risk.yml
194
200
  - lib/pg_reports/definitions/tables/bloated_tables.yml
195
201
  - lib/pg_reports/definitions/tables/cache_hit_ratios.yml
196
202
  - lib/pg_reports/definitions/tables/recently_modified.yml
197
203
  - lib/pg_reports/definitions/tables/row_counts.yml
198
204
  - lib/pg_reports/definitions/tables/seq_scans.yml
199
205
  - lib/pg_reports/definitions/tables/table_sizes.yml
206
+ - lib/pg_reports/definitions/tables/tables_without_pk.yml
200
207
  - lib/pg_reports/definitions/tables/vacuum_needed.yml
201
208
  - lib/pg_reports/engine.rb
202
209
  - lib/pg_reports/error.rb
@@ -226,8 +233,11 @@ files:
226
233
  - lib/pg_reports/sql/connections/pool_wait_times.sql
227
234
  - lib/pg_reports/sql/indexes/bloated_indexes.sql
228
235
  - lib/pg_reports/sql/indexes/duplicate_indexes.sql
236
+ - lib/pg_reports/sql/indexes/fk_without_indexes.sql
237
+ - lib/pg_reports/sql/indexes/index_correlation.sql
229
238
  - lib/pg_reports/sql/indexes/index_sizes.sql
230
239
  - lib/pg_reports/sql/indexes/index_usage.sql
240
+ - lib/pg_reports/sql/indexes/inefficient_indexes.sql
231
241
  - lib/pg_reports/sql/indexes/invalid_indexes.sql
232
242
  - lib/pg_reports/sql/indexes/missing_indexes.sql
233
243
  - lib/pg_reports/sql/indexes/unused_indexes.sql
@@ -237,20 +247,25 @@ files:
237
247
  - lib/pg_reports/sql/queries/low_cache_hit_queries.sql
238
248
  - lib/pg_reports/sql/queries/missing_index_queries.sql
239
249
  - lib/pg_reports/sql/queries/slow_queries.sql
250
+ - lib/pg_reports/sql/queries/temp_file_queries.sql
240
251
  - lib/pg_reports/sql/schema_analysis/unique_indexes.sql
241
252
  - lib/pg_reports/sql/system/activity_overview.sql
242
253
  - lib/pg_reports/sql/system/cache_stats.sql
254
+ - lib/pg_reports/sql/system/checkpoint_stats.sql
255
+ - lib/pg_reports/sql/system/checkpoint_stats_legacy.sql
243
256
  - lib/pg_reports/sql/system/database_sizes.sql
244
257
  - lib/pg_reports/sql/system/databases_list.sql
245
258
  - lib/pg_reports/sql/system/extensions.sql
246
259
  - lib/pg_reports/sql/system/live_metrics.sql
247
260
  - lib/pg_reports/sql/system/settings.sql
261
+ - lib/pg_reports/sql/system/wraparound_risk.sql
248
262
  - lib/pg_reports/sql/tables/bloated_tables.sql
249
263
  - lib/pg_reports/sql/tables/cache_hit_ratios.sql
250
264
  - lib/pg_reports/sql/tables/recently_modified.sql
251
265
  - lib/pg_reports/sql/tables/row_counts.sql
252
266
  - lib/pg_reports/sql/tables/seq_scans.sql
253
267
  - lib/pg_reports/sql/tables/table_sizes.sql
268
+ - lib/pg_reports/sql/tables/tables_without_pk.sql
254
269
  - lib/pg_reports/sql/tables/vacuum_needed.sql
255
270
  - lib/pg_reports/sql_loader.rb
256
271
  - lib/pg_reports/telegram_sender.rb