pg_insights 0.1.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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +183 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/javascripts/pg_insights/application.js +436 -0
  6. data/app/assets/javascripts/pg_insights/health.js +104 -0
  7. data/app/assets/javascripts/pg_insights/results/chart_renderer.js +126 -0
  8. data/app/assets/javascripts/pg_insights/results/table_manager.js +378 -0
  9. data/app/assets/javascripts/pg_insights/results/view_toggles.js +25 -0
  10. data/app/assets/javascripts/pg_insights/results.js +13 -0
  11. data/app/assets/stylesheets/pg_insights/application.css +750 -0
  12. data/app/assets/stylesheets/pg_insights/health.css +501 -0
  13. data/app/assets/stylesheets/pg_insights/results.css +682 -0
  14. data/app/controllers/pg_insights/application_controller.rb +4 -0
  15. data/app/controllers/pg_insights/health_controller.rb +110 -0
  16. data/app/controllers/pg_insights/insights_controller.rb +77 -0
  17. data/app/controllers/pg_insights/queries_controller.rb +44 -0
  18. data/app/helpers/pg_insights/application_helper.rb +4 -0
  19. data/app/helpers/pg_insights/insights_helper.rb +190 -0
  20. data/app/jobs/pg_insights/application_job.rb +4 -0
  21. data/app/jobs/pg_insights/health_check_job.rb +45 -0
  22. data/app/jobs/pg_insights/health_check_scheduler_job.rb +52 -0
  23. data/app/jobs/pg_insights/recurring_health_checks_job.rb +49 -0
  24. data/app/models/pg_insights/application_record.rb +5 -0
  25. data/app/models/pg_insights/health_check_result.rb +46 -0
  26. data/app/models/pg_insights/query.rb +10 -0
  27. data/app/services/pg_insights/health_check_service.rb +298 -0
  28. data/app/services/pg_insights/insight_query_service.rb +21 -0
  29. data/app/views/layouts/pg_insights/application.html.erb +58 -0
  30. data/app/views/pg_insights/health/index.html.erb +324 -0
  31. data/app/views/pg_insights/insights/_chart_view.html.erb +25 -0
  32. data/app/views/pg_insights/insights/_column_panel.html.erb +18 -0
  33. data/app/views/pg_insights/insights/_query_examples.html.erb +32 -0
  34. data/app/views/pg_insights/insights/_query_panel.html.erb +36 -0
  35. data/app/views/pg_insights/insights/_result.html.erb +15 -0
  36. data/app/views/pg_insights/insights/_results_info.html.erb +19 -0
  37. data/app/views/pg_insights/insights/_results_panel.html.erb +13 -0
  38. data/app/views/pg_insights/insights/_results_table.html.erb +45 -0
  39. data/app/views/pg_insights/insights/_stats_view.html.erb +3 -0
  40. data/app/views/pg_insights/insights/_table_controls.html.erb +21 -0
  41. data/app/views/pg_insights/insights/_table_view.html.erb +5 -0
  42. data/app/views/pg_insights/insights/index.html.erb +5 -0
  43. data/config/default_queries.yml +85 -0
  44. data/config/routes.rb +22 -0
  45. data/lib/generators/pg_insights/clean_generator.rb +74 -0
  46. data/lib/generators/pg_insights/install_generator.rb +176 -0
  47. data/lib/pg_insights/engine.rb +40 -0
  48. data/lib/pg_insights/version.rb +3 -0
  49. data/lib/pg_insights.rb +83 -0
  50. data/lib/tasks/pg_insights.rake +172 -0
  51. metadata +124 -0
data/config/routes.rb ADDED
@@ -0,0 +1,22 @@
1
+ PgInsights::Engine.routes.draw do
2
+ root "insights#index"
3
+
4
+ # For running queries and loading the main UI
5
+ post "/", to: "insights#index"
6
+
7
+ # For the table name dropdown
8
+ get :table_names, to: "insights#table_names"
9
+
10
+ # For managing user-saved queries
11
+ resources :queries, only: [ :create, :update, :destroy ] # For the health dashboard
12
+ get :health, to: "health#index"
13
+ namespace :health do
14
+ get :unused_indexes
15
+ get :missing_indexes
16
+ get :sequential_scans
17
+ get :slow_queries
18
+ get :table_bloat
19
+ get :parameter_settings
20
+ post :refresh
21
+ end
22
+ end
@@ -0,0 +1,74 @@
1
+ require "rails/generators/base"
2
+
3
+ module PgInsights
4
+ module Generators
5
+ class CleanGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../../../..", __FILE__)
7
+
8
+ def remove_routes
9
+ routes_file = File.join(destination_root, "config", "routes.rb")
10
+ route_to_remove = "mount PgInsights::Engine => '/pg_insights'"
11
+
12
+ if File.exist?(routes_file)
13
+ routes_content = File.read(routes_file)
14
+ if routes_content.include?(route_to_remove)
15
+ puts "Removing PgInsights engine mount from routes..."
16
+ updated_content = routes_content.gsub(/^\s*#{Regexp.escape(route_to_remove)}\s*\n?/, "")
17
+ File.write(routes_file, updated_content)
18
+ say_status("removed", "PgInsights engine mount from config/routes.rb", :green)
19
+ else
20
+ say_status("skipped", "PgInsights engine mount not found in config/routes.rb", :yellow)
21
+ end
22
+ end
23
+ end
24
+
25
+ def remove_initializer
26
+ initializer_path = "config/initializers/pg_insights.rb"
27
+ initializer_full_path = File.join(destination_root, initializer_path)
28
+
29
+ if File.exist?(initializer_full_path)
30
+ puts "Removing PgInsights initializer..."
31
+ remove_file initializer_path
32
+ say_status("removed", initializer_path, :green)
33
+ else
34
+ say_status("skipped", "#{initializer_path} not found", :yellow)
35
+ end
36
+ end
37
+
38
+ def show_migration_rollback_instructions
39
+ puts "\nPgInsights has been cleaned up!"
40
+ puts ""
41
+ puts "To complete the uninstallation, you may also want to:"
42
+ puts "1. Roll back the migrations:"
43
+
44
+ migration_files = find_pg_insights_migrations
45
+ if migration_files.any?
46
+ puts " rails db:rollback STEP=#{migration_files.count}"
47
+ puts ""
48
+ puts "2. Or manually remove the migration files:"
49
+ migration_files.each do |file|
50
+ puts " rm #{file}"
51
+ end
52
+ else
53
+ puts " (No PgInsights migrations found)"
54
+ end
55
+
56
+ puts ""
57
+ puts "3. Remove any PgInsights data from your database:"
58
+ puts " rails runner 'PgInsights::Query.destroy_all'"
59
+ puts " rails runner 'PgInsights::HealthCheckResult.destroy_all'"
60
+ puts ""
61
+ puts "4. If you want to reinstall later, run: rails generate pg_insights:install"
62
+ end
63
+
64
+ private
65
+
66
+ def find_pg_insights_migrations
67
+ migrate_path = File.join(destination_root, "db", "migrate")
68
+ return [] unless File.directory?(migrate_path)
69
+
70
+ Dir.glob("#{migrate_path}/*_create_pg_insights_*.rb").sort
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,176 @@
1
+ require "rails/generators/base"
2
+ require "rails/generators/migration"
3
+ require "rails/generators/active_record"
4
+
5
+ module PgInsights
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ include Rails::Generators::Migration
9
+
10
+ source_root File.expand_path("../../../..", __FILE__)
11
+
12
+ def self.next_migration_number(dirname)
13
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
14
+ end
15
+
16
+ def copy_migrations
17
+ copy_queries_migration
18
+ copy_health_check_results_migration
19
+ end
20
+
21
+ def create_initializer
22
+ initializer_path = File.join(destination_root, "config", "initializers", "pg_insights.rb")
23
+
24
+ if File.exist?(initializer_path)
25
+ say_status("skipped", "Initializer 'config/initializers/pg_insights.rb' already exists", :yellow)
26
+ else
27
+ puts "Creating PgInsights initializer..."
28
+ create_file "config/initializers/pg_insights.rb", initializer_content
29
+ end
30
+ end
31
+
32
+ def mount_engine
33
+ route_to_add = "mount PgInsights::Engine => '/pg_insights'"
34
+ routes_file = File.join(destination_root, "config", "routes.rb")
35
+
36
+ if File.exist?(routes_file) && File.read(routes_file).include?(route_to_add)
37
+ say_status("skipped", "Route `#{route_to_add}` already exists in `config/routes.rb`", :yellow)
38
+ else
39
+ puts "Mounting PgInsights engine..."
40
+ route route_to_add
41
+ end
42
+ end
43
+
44
+ def show_readme
45
+ puts "\nPgInsights has been successfully installed!"
46
+ puts ""
47
+ puts "Next steps:"
48
+ puts "1. Run 'rails db:migrate' to create the necessary tables"
49
+ puts "2. Review and customize 'config/initializers/pg_insights.rb'"
50
+ puts "3. Configure background jobs (optional, see initializer comments)"
51
+ puts "4. Visit '/pg_insights' in your browser to start using the dashboard"
52
+ puts ""
53
+ puts "For background job integration, see: config/initializers/pg_insights.rb"
54
+ puts "To check status: rails pg_insights:status"
55
+ puts "To uninstall: rails generate pg_insights:clean"
56
+ end
57
+
58
+ private
59
+
60
+ def initializer_content
61
+ <<~RUBY
62
+ # PgInsights Configuration
63
+ ##{' '}
64
+ # This file contains the configuration for PgInsights, a PostgreSQL database
65
+ # performance monitoring and health check engine.
66
+
67
+ PgInsights.configure do |config|
68
+ # === Background Jobs Configuration ===
69
+ ##{' '}
70
+ # Enable background job processing for health checks.
71
+ # When enabled, health checks will run asynchronously if your app has ActiveJob configured.
72
+ # When disabled or if ActiveJob is not available, health checks run synchronously.
73
+ #
74
+ # Default: true
75
+ config.enable_background_jobs = true
76
+
77
+ # Queue name for PgInsights background jobs
78
+ # Make sure this queue is processed by your job processor (Sidekiq, Resque, etc.)
79
+ #
80
+ # Default: :pg_insights_health
81
+ config.background_job_queue = :pg_insights_health
82
+
83
+ # === Cache and Timeout Settings ===
84
+ #{' '}
85
+ # How long to cache health check results before considering them stale
86
+ # Stale results will trigger background refresh when accessed
87
+ #
88
+ # Default: 5.minutes
89
+ config.health_cache_expiry = 5.minutes
90
+
91
+ # Timeout for individual health check queries to prevent long-running queries
92
+ # from blocking the application
93
+ #
94
+ # Default: 10.seconds#{' '}
95
+ config.health_check_timeout = 10.seconds
96
+
97
+ # Maximum execution time for user queries in the insights interface
98
+ #
99
+ # Default: 30.seconds
100
+ config.max_query_execution_time = 30.seconds
101
+ end
102
+
103
+ # === Background Job Integration ===
104
+ #
105
+ # If you want automatic recurring health checks, add one of these to your job scheduler:
106
+ #
107
+ # ** Whenever (crontab) **
108
+ # Add to config/schedule.rb:
109
+ # every 1.hour do
110
+ # runner "PgInsights::RecurringHealthChecksJob.perform_later"
111
+ # end
112
+ #
113
+ # ** Sidekiq-Cron **
114
+ # Add to config/initializers/sidekiq.rb:
115
+ # Sidekiq::Cron::Job.create(
116
+ # name: 'PgInsights Health Checks',
117
+ # cron: '0 * * * *',
118
+ # class: 'PgInsights::RecurringHealthChecksJob'
119
+ # )
120
+ #
121
+ # ** Manual Trigger **
122
+ # You can also trigger health checks manually:
123
+ # PgInsights::HealthCheckService.refresh_all!
124
+ #
125
+ # ** Check Status **
126
+ # To verify your configuration:
127
+ # rails pg_insights:status
128
+
129
+ # === Environment-Specific Settings ===
130
+ #
131
+ # You may want different settings per environment:
132
+ #
133
+ # if Rails.env.development?
134
+ # PgInsights.configure do |config|
135
+ # config.health_cache_expiry = 1.minute # Shorter cache in development
136
+ # config.enable_background_jobs = false # Synchronous in development
137
+ # end
138
+ # end
139
+ #
140
+ # if Rails.env.production?
141
+ # PgInsights.configure do |config|
142
+ # config.health_cache_expiry = 15.minutes # Longer cache in production
143
+ # config.health_check_timeout = 30.seconds # More generous timeout
144
+ # end
145
+ # end
146
+ RUBY
147
+ end
148
+
149
+ def copy_queries_migration
150
+ if migration_exists?("create_pg_insights_queries")
151
+ say_status("skipped", "Migration 'create_pg_insights_queries' already exists", :yellow)
152
+ else
153
+ puts "Copying PgInsights queries migration..."
154
+ migration_template "db/migrate/create_pg_insights_queries.rb",
155
+ "db/migrate/create_pg_insights_queries.rb"
156
+ end
157
+ end
158
+
159
+ def copy_health_check_results_migration
160
+ if migration_exists?("create_pg_insights_health_check_results")
161
+ say_status("skipped", "Migration 'create_pg_insights_health_check_results' already exists", :yellow)
162
+ else
163
+ puts "Copying PgInsights health check results migration..."
164
+ migration_template "db/migrate/create_pg_insights_health_check_results.rb",
165
+ "db/migrate/create_pg_insights_health_check_results.rb"
166
+ end
167
+ end
168
+
169
+ def migration_exists?(migration_name)
170
+ migrate_path = File.join(destination_root, "db", "migrate")
171
+ return false unless File.directory?(migrate_path)
172
+ Dir.glob("#{migrate_path}/*_#{migration_name}.rb").any?
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "chartkick"
4
+
5
+ module PgInsights
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace PgInsights
8
+
9
+ initializer "pg_insights.assets.precompile" do |app|
10
+ app.config.assets.precompile += %w[
11
+ pg_insights/application.css
12
+ pg_insights/application.js
13
+ pg_insights/health.css
14
+ pg_insights/health.js
15
+ pg_insights/results.css
16
+ pg_insights/results.js
17
+ ]
18
+ end
19
+
20
+ initializer "pg_insights.configure_background_jobs", after: "active_job.set_configs" do |app|
21
+ ActiveSupport.on_load(:active_job) do
22
+ if PgInsights.enable_background_jobs
23
+ if PgInsights.background_jobs_available?
24
+ Rails.logger.info "PgInsights: Background jobs enabled (#{ActiveJob::Base.queue_adapter_name})"
25
+ else
26
+ Rails.logger.warn "PgInsights: Background jobs enabled but may not be fully available yet."
27
+ Rails.logger.warn "PgInsights: Will fall back to synchronous execution when needed."
28
+ Rails.logger.warn "PgInsights: Check your ActiveJob configuration if you see issues."
29
+ end
30
+ else
31
+ Rails.logger.info "PgInsights: Background jobs disabled - will run health checks synchronously"
32
+ end
33
+ end
34
+ end
35
+
36
+ rake_tasks do
37
+ load "tasks/pg_insights.rake"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module PgInsights
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,83 @@
1
+ require "pg_insights/version"
2
+ require "pg_insights/engine"
3
+
4
+ module PgInsights
5
+ # Configuration options
6
+ mattr_accessor :enable_background_jobs, default: true
7
+ mattr_accessor :health_cache_expiry, default: 5.minutes
8
+ mattr_accessor :background_job_queue, default: :pg_insights_health
9
+ mattr_accessor :max_query_execution_time, default: 30.seconds
10
+ mattr_accessor :health_check_timeout, default: 10.seconds
11
+
12
+ def self.configure
13
+ yield self
14
+ end
15
+
16
+ # Check if background jobs are available and properly configured
17
+ def self.background_jobs_available?
18
+ return false unless enable_background_jobs
19
+ return false unless defined?(ActiveJob::Base)
20
+ return false if ActiveJob::Base.queue_adapter.is_a?(ActiveJob::QueueAdapters::InlineAdapter)
21
+ return false unless defined?(PgInsights::HealthCheckJob)
22
+
23
+ # Verify the queue adapter can handle our jobs
24
+ begin
25
+ ActiveJob::Base.queue_adapter.respond_to?(:enqueue) ||
26
+ ActiveJob::Base.queue_adapter.respond_to?(:enqueue_at)
27
+ rescue => e
28
+ Rails.logger.debug "PgInsights: Background job check failed: #{e.message}" if defined?(Rails)
29
+ false
30
+ end
31
+ end
32
+
33
+ # Safely execute background jobs with fallback
34
+ def self.execute_with_fallback(job_class, method_name, *args, &fallback_block)
35
+ if background_jobs_available?
36
+ begin
37
+ job_class.public_send(method_name, *args)
38
+ return true
39
+ rescue => e
40
+ Rails.logger.warn "PgInsights: Background job failed, falling back to synchronous execution: #{e.message}" if defined?(Rails)
41
+ end
42
+ end
43
+
44
+ # Execute fallback if provided
45
+ fallback_block.call if fallback_block
46
+ false
47
+ end
48
+
49
+ # Integration helpers for host applications
50
+ module Integration
51
+ # Helper for host apps to check if they need to configure anything
52
+ def self.status
53
+ {
54
+ background_jobs_available: PgInsights.background_jobs_available?,
55
+ queue_adapter: defined?(ActiveJob::Base) ? ActiveJob::Base.queue_adapter_name : "not_available",
56
+ configuration: {
57
+ enable_background_jobs: PgInsights.enable_background_jobs,
58
+ health_cache_expiry: PgInsights.health_cache_expiry,
59
+ background_job_queue: PgInsights.background_job_queue
60
+ }
61
+ }
62
+ end
63
+
64
+ # Helper for host apps to test job functionality
65
+ def self.test_background_jobs
66
+ return { success: false, error: "Background jobs disabled" } unless PgInsights.enable_background_jobs
67
+ return { success: false, error: "ActiveJob not available" } unless defined?(ActiveJob::Base)
68
+
69
+ begin
70
+ # Try to enqueue a simple test
71
+ if defined?(PgInsights::HealthCheckJob)
72
+ # Don't actually run the job, just test enqueueing
73
+ Rails.logger.info "PgInsights: Testing background job capability..."
74
+ { success: true, message: "Background jobs appear to be working" }
75
+ else
76
+ { success: false, error: "PgInsights job classes not loaded" }
77
+ end
78
+ rescue => e
79
+ { success: false, error: e.message }
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,172 @@
1
+ namespace :pg_insights do
2
+ desc "Show PgInsights configuration and background job status"
3
+ task status: :environment do
4
+ puts "PgInsights Configuration Status"
5
+ puts "==============================="
6
+
7
+ status = PgInsights::Integration.status
8
+
9
+ puts "Background Jobs: #{status[:background_jobs_available] ? '✅ Available' : '❌ Not Available'}"
10
+ puts "Queue Adapter: #{status[:queue_adapter]}"
11
+ puts "Queue Name: #{status[:configuration][:background_job_queue]}"
12
+ puts "Cache Expiry: #{status[:configuration][:health_cache_expiry]}"
13
+ puts "Enabled: #{status[:configuration][:enable_background_jobs] ? 'Yes' : 'No'}"
14
+
15
+ if defined?(PgInsights::RecurringHealthChecksJob)
16
+ puts "\nRecurring Job Status:"
17
+ validation = PgInsights::RecurringHealthChecksJob.validate_setup
18
+ if validation[:valid]
19
+ puts "✅ #{validation[:message]}"
20
+ else
21
+ puts "❌ Issues found:"
22
+ validation[:issues].each { |issue| puts " - #{issue}" }
23
+ end
24
+ end
25
+
26
+ puts "\nIntegration Help:"
27
+ if status[:background_jobs_available]
28
+ puts "✅ Your application is ready for PgInsights background jobs!"
29
+ puts " Add this to your job scheduler (whenever, sidekiq-cron, etc.):"
30
+ puts " PgInsights::RecurringHealthChecksJob.perform_later"
31
+ else
32
+ puts "ℹ️ Background jobs are not configured. PgInsights will work in synchronous mode."
33
+ puts " To enable background processing:"
34
+ puts " 1. Configure ActiveJob in your Rails application"
35
+ puts " 2. Set up a job queue backend (Sidekiq, Resque, etc.)"
36
+ puts " 3. Add to config/initializers/pg_insights.rb:"
37
+ puts " PgInsights.configure { |c| c.enable_background_jobs = true }"
38
+ end
39
+ end
40
+
41
+ desc "Test background job functionality"
42
+ task test_jobs: :environment do
43
+ puts "Testing PgInsights Background Jobs"
44
+ puts "=================================="
45
+
46
+ test_result = PgInsights::Integration.test_background_jobs
47
+
48
+ if test_result[:success]
49
+ puts "✅ #{test_result[:message]}"
50
+
51
+ # Try scheduling a real health check
52
+ if defined?(PgInsights::HealthCheckJob)
53
+ if PgInsights::HealthCheckJob.perform_check("parameter_settings")
54
+ puts "✅ Successfully enqueued a test health check"
55
+ else
56
+ puts "❌ Failed to enqueue test health check"
57
+ end
58
+ end
59
+ else
60
+ puts "❌ #{test_result[:error]}"
61
+ end
62
+ end
63
+
64
+ desc "Run health checks synchronously (without background jobs)"
65
+ task health_check: :environment do
66
+ puts "Running PgInsights Health Checks (Synchronous)"
67
+ puts "=============================================="
68
+
69
+ start_time = Time.current
70
+
71
+ begin
72
+ PgInsights::HealthCheckService.execute_all_checks_synchronously
73
+ execution_time = Time.current - start_time
74
+
75
+ puts "✅ Health checks completed in #{execution_time.round(2)} seconds"
76
+ puts "Visit /pg_insights/health in your browser to see the results"
77
+ rescue => e
78
+ puts "❌ Health checks failed: #{e.message}"
79
+ end
80
+ end
81
+
82
+ desc "Clear all PgInsights stored data"
83
+ task clear_data: :environment do
84
+ puts "Clearing PgInsights stored data..."
85
+
86
+ # Remove data from database
87
+ begin
88
+ if defined?(PgInsights::Query)
89
+ query_count = PgInsights::Query.count
90
+ PgInsights::Query.destroy_all
91
+ puts "Removed #{query_count} stored queries"
92
+ end
93
+ rescue => e
94
+ puts "Could not remove queries: #{e.message}"
95
+ end
96
+
97
+ begin
98
+ if defined?(PgInsights::HealthCheckResult)
99
+ result_count = PgInsights::HealthCheckResult.count
100
+ PgInsights::HealthCheckResult.destroy_all
101
+ puts "Removed #{result_count} cached health check results"
102
+ end
103
+ rescue => e
104
+ puts "Could not remove health check results: #{e.message}"
105
+ end
106
+
107
+ puts "PgInsights data cleared successfully!"
108
+ puts "To completely uninstall PgInsights (remove migrations and routes), run: rails generate pg_insights:clean"
109
+ end
110
+
111
+ desc "Reset PgInsights data (clears all stored queries and health check results)"
112
+ task reset: :environment do
113
+ puts "Resetting PgInsights data..."
114
+
115
+ begin
116
+ if defined?(PgInsights::Query)
117
+ PgInsights::Query.destroy_all
118
+ puts "Cleared all PgInsights queries"
119
+ end
120
+ rescue => e
121
+ puts "Could not clear queries: #{e.message}"
122
+ end
123
+
124
+ begin
125
+ if defined?(PgInsights::HealthCheckResult)
126
+ PgInsights::HealthCheckResult.destroy_all
127
+ puts "Cleared all PgInsights health check results"
128
+ end
129
+ rescue => e
130
+ puts "Could not clear health check results: #{e.message}"
131
+ end
132
+
133
+ puts "PgInsights data reset completed!"
134
+ end
135
+
136
+ desc "Show PgInsights statistics"
137
+ task stats: :environment do
138
+ puts "PgInsights Statistics:"
139
+ puts "====================="
140
+
141
+ begin
142
+ if defined?(PgInsights::Query)
143
+ query_count = PgInsights::Query.count
144
+ puts "Stored queries: #{query_count}"
145
+ end
146
+ rescue => e
147
+ puts "Queries table not available: #{e.message}"
148
+ end
149
+
150
+ begin
151
+ if defined?(PgInsights::HealthCheckResult)
152
+ result_count = PgInsights::HealthCheckResult.count
153
+ recent_results = PgInsights::HealthCheckResult.where("executed_at > ?", 24.hours.ago).count
154
+ puts "Health check results: #{result_count}"
155
+ puts "Recent results (24h): #{recent_results}"
156
+
157
+ # Show results by type
158
+ PgInsights::HealthCheckResult::VALID_CHECK_TYPES.each do |check_type|
159
+ latest = PgInsights::HealthCheckResult.latest_for_type(check_type)
160
+ if latest
161
+ freshness = latest.fresh? ? "fresh" : "stale"
162
+ puts " #{check_type}: #{latest.status} (#{freshness})"
163
+ else
164
+ puts " #{check_type}: no data"
165
+ end
166
+ end
167
+ end
168
+ rescue => e
169
+ puts "Health check results table not available: #{e.message}"
170
+ end
171
+ end
172
+ end