Module: PgReports::Dashboard::ReportsRegistry
- Defined in:
- lib/pg_reports/dashboard/reports_registry.rb
Overview
Registry of all available reports for the dashboard
Constant Summary collapse
- REPORT_CONFIG =
Thresholds and problem field configuration for each report Text documentation is stored in I18n locale files (config/locales/*.yml)
{ # === QUERIES === slow_queries: { thresholds: {mean_time_ms: {warning: 100, critical: 500}}, problem_fields: ["mean_time_ms"] }, heavy_queries: { thresholds: {calls: {warning: 10000, critical: 100000}}, problem_fields: ["calls"] }, expensive_queries: { thresholds: {total_time_ms: {warning: 60000, critical: 300000}}, problem_fields: ["total_time_ms"] }, missing_index_queries: { thresholds: {seq_tup_read: {warning: 100000, critical: 1000000}}, problem_fields: ["seq_tup_read", "seq_scan"] }, low_cache_hit_queries: { thresholds: {cache_hit_ratio: {warning: 0.95, critical: 0.80, inverted: true}}, problem_fields: ["cache_hit_ratio"] }, all_queries: { thresholds: {}, problem_fields: [] }, temp_file_queries: { thresholds: {temp_mb_written: {warning: 100, critical: 1000}}, problem_fields: ["temp_mb_written"] }, # === INDEXES === unused_indexes: { thresholds: {idx_scan: {warning: 10, critical: 0, inverted: true}}, problem_fields: ["idx_scan"] }, duplicate_indexes: { thresholds: {}, problem_fields: [] }, invalid_indexes: { thresholds: {}, problem_fields: [] }, missing_indexes: { thresholds: {seq_scan_ratio: {warning: 0.5, critical: 0.9}}, problem_fields: ["seq_scan", "seq_tup_read"] }, inefficient_indexes: { thresholds: {read_to_fetch_ratio: {warning: 10, critical: 100}}, problem_fields: ["read_to_fetch_ratio"] }, index_usage: { thresholds: {}, problem_fields: [] }, bloated_indexes: { thresholds: {bloat_ratio: {warning: 0.3, critical: 0.5}}, problem_fields: ["bloat_ratio", "bloat_size"] }, index_sizes: { thresholds: {size_bytes: {warning: 1073741824, critical: 10737418240}}, problem_fields: ["size_bytes"] }, fk_without_indexes: { thresholds: {}, problem_fields: ["child_table_size_mb"] }, index_correlation: { thresholds: {correlation: {warning: 0.5, critical: 0.2, inverted: true}}, problem_fields: ["correlation"] }, # === TABLES === table_sizes: { thresholds: {total_size_bytes: {warning: 10737418240, critical: 107374182400}}, problem_fields: ["total_size_bytes"] }, bloated_tables: { thresholds: {dead_tuple_ratio: {warning: 0.1, critical: 0.2}}, problem_fields: ["dead_tuple_ratio", "n_dead_tup"] }, vacuum_needed: { thresholds: {n_dead_tup: {warning: 10000, critical: 100000}}, problem_fields: ["n_dead_tup"] }, row_counts: { thresholds: {}, problem_fields: [] }, cache_hit_ratios: { thresholds: {cache_hit_ratio: {warning: 0.95, critical: 0.80, inverted: true}}, problem_fields: ["cache_hit_ratio"] }, seq_scans: { thresholds: {seq_scan: {warning: 1000, critical: 10000}}, problem_fields: ["seq_scan", "seq_tup_read"] }, recently_modified: { thresholds: {}, problem_fields: [] }, tables_without_pk: { thresholds: {}, problem_fields: [] }, # === CONNECTIONS === active_connections: { thresholds: {connection_count: {warning: 50, critical: 100}}, problem_fields: ["connection_count"] }, connection_stats: { thresholds: {idle_in_transaction: {warning: 5, critical: 20}}, problem_fields: ["idle_in_transaction"] }, long_running_queries: { thresholds: {duration_seconds: {warning: 60, critical: 300}}, problem_fields: ["duration_seconds", "duration"] }, blocking_queries: { thresholds: {blocked_count: {warning: 1, critical: 5}}, problem_fields: ["blocked_count"] }, locks: { thresholds: {waiting_locks: {warning: 5, critical: 20}}, problem_fields: ["waiting_locks"] }, idle_connections: { thresholds: {idle_count: {warning: 30, critical: 80}}, problem_fields: ["idle_count"] }, pool_usage: { thresholds: {utilization_pct: {warning: 70, critical: 85}}, problem_fields: ["utilization_pct"] }, pool_wait_times: { thresholds: {wait_duration_seconds: {warning: 10, critical: 60}}, problem_fields: ["wait_duration_seconds"] }, pool_saturation: { thresholds: {utilization_pct: {warning: 70, critical: 85}}, problem_fields: ["utilization_pct"] }, connection_churn: { thresholds: { churn_rate_pct: {warning: 50, critical: 75}, short_lived_connections: {warning: 10, critical: 25} }, problem_fields: ["churn_rate_pct", "short_lived_connections"] }, # === SYSTEM === database_sizes: { thresholds: {size_bytes: {warning: 10737418240, critical: 107374182400}}, problem_fields: ["size_bytes"] }, settings: { thresholds: {}, problem_fields: [] }, extensions: { thresholds: {}, problem_fields: [] }, activity_overview: { thresholds: {}, problem_fields: [] }, cache_stats: { thresholds: {cache_hit_ratio: {warning: 0.95, critical: 0.90, inverted: true}}, problem_fields: ["cache_hit_ratio"] }, wraparound_risk: { thresholds: {pct_towards_wraparound: {warning: 50, critical: 75}}, problem_fields: ["pct_towards_wraparound"] }, checkpoint_stats: { thresholds: {requested_pct: {warning: 50, critical: 75}}, problem_fields: ["requested_pct"] }, # === SCHEMA ANALYSIS === missing_validations: { thresholds: {}, problem_fields: [] }, unused_columns: { thresholds: {}, problem_fields: ["column_name"] }, always_null_columns: { thresholds: {null_pct: {warning: 99, critical: 100}}, problem_fields: ["column_name", "null_pct"] }, polymorphic_without_index: { thresholds: {}, problem_fields: ["coverage"] }, counter_cache_issues: { thresholds: {}, problem_fields: ["issue", "expected_column"] }, soft_delete_without_scope: { thresholds: {}, problem_fields: ["status", "soft_delete_column"] }, orphan_tables: { thresholds: {}, problem_fields: ["classification"] }, # === TABLES (extra) === update_hotspots: { thresholds: { updates_per_row: {warning: 10, critical: 100}, hot_update_pct: {warning: 50, critical: 20, inverted: true} }, problem_fields: ["updates_per_row", "hot_update_pct"] }, unused_tables: { thresholds: {total_size_mb: {warning: 10, critical: 100}}, problem_fields: ["table_name"] } }.freeze
- REPORTS =
{ queries: { name: "Queries", icon: "⚡", color: "#6366f1", reports: { slow_queries: {name: "Slow Queries", description: "Queries with high mean execution time"}, heavy_queries: {name: "Heavy Queries", description: "Most frequently called queries"}, expensive_queries: {name: "Expensive Queries", description: "Queries consuming most total time"}, missing_index_queries: {name: "Missing Index Queries", description: "Queries potentially missing indexes"}, low_cache_hit_queries: {name: "Low Cache Hit", description: "Queries with poor cache utilization"}, temp_file_queries: {name: "Temp File Queries", description: "Queries spilling to disk"}, all_queries: {name: "All Queries", description: "All query statistics"} } }, indexes: { name: "Indexes", icon: "📇", color: "#10b981", reports: { unused_indexes: {name: "Unused Indexes", description: "Indexes rarely or never scanned"}, duplicate_indexes: {name: "Duplicate Indexes", description: "Redundant indexes"}, invalid_indexes: {name: "Invalid Indexes", description: "Indexes that failed to build"}, missing_indexes: {name: "Missing Indexes", description: "Tables potentially missing indexes"}, inefficient_indexes: {name: "Inefficient Indexes", description: "Indexes with high read-to-fetch ratio"}, index_usage: {name: "Index Usage", description: "Index scan statistics"}, bloated_indexes: {name: "Bloated Indexes", description: "Indexes with high bloat"}, fk_without_indexes: {name: "FK Without Indexes", description: "Foreign keys missing indexes"}, index_correlation: {name: "Index Correlation", description: "Low physical correlation indexes"}, index_sizes: {name: "Index Sizes", description: "Index disk usage"} } }, tables: { name: "Tables", icon: "📊", color: "#f59e0b", reports: { table_sizes: {name: "Table Sizes", description: "Table disk usage"}, bloated_tables: {name: "Bloated Tables", description: "Tables with high dead tuple ratio"}, vacuum_needed: {name: "Vacuum Needed", description: "Tables needing vacuum"}, row_counts: {name: "Row Counts", description: "Table row counts"}, cache_hit_ratios: {name: "Cache Hit Ratios", description: "Table cache statistics"}, seq_scans: {name: "Sequential Scans", description: "Tables with high sequential scans"}, tables_without_pk: {name: "No Primary Key", description: "Tables missing primary keys"}, recently_modified: {name: "Recently Modified", description: "Tables with recent activity"}, update_hotspots: {name: "Update Hotspots", description: "Same rows or indexed columns updated repeatedly", new: true}, unused_tables: {name: "Unused Tables", description: "Tables never queried since the last stats reset", new: true} } }, connections: { name: "Connections", icon: "🔌", color: "#ec4899", reports: { active_connections: {name: "Active Connections", description: "Current database connections"}, connection_stats: {name: "Connection Stats", description: "Connection statistics by state"}, long_running_queries: {name: "Long Running", description: "Queries running for extended period"}, blocking_queries: {name: "Blocking Queries", description: "Queries blocking others"}, locks: {name: "Locks", description: "Current database locks"}, idle_connections: {name: "Idle Connections", description: "Idle connections"}, pool_usage: {name: "Pool Usage", description: "Connection pool utilization"}, pool_wait_times: {name: "Wait Times", description: "Resource wait analysis"}, pool_saturation: {name: "Pool Saturation", description: "Pool health warnings"}, connection_churn: {name: "Connection Churn", description: "Connection lifecycle analysis"} } }, system: { name: "System", icon: "🖥️", color: "#8b5cf6", reports: { database_sizes: {name: "Database Sizes", description: "Size of all databases"}, settings: {name: "Settings", description: "PostgreSQL configuration"}, extensions: {name: "Extensions", description: "Installed extensions"}, activity_overview: {name: "Activity Overview", description: "Current activity summary"}, wraparound_risk: {name: "Wraparound Risk", description: "Transaction ID wraparound proximity"}, checkpoint_stats: {name: "Checkpoint Stats", description: "Checkpoint and bgwriter statistics"}, cache_stats: {name: "Cache Stats", description: "Database cache statistics"} } }, schema_analysis: { name: "Schema Analysis", icon: "🔍", color: "#06b6d4", reports: { missing_validations: {name: "Missing Validations", description: "Unique indexes without model validations"}, unused_columns: {name: "Unused Columns", description: "Columns that have only ever held a single value", new: true}, always_null_columns: {name: "Always-NULL Columns", description: "Nullable columns that contain only NULL", new: true}, polymorphic_without_index: {name: "Polymorphic Without Index", description: "Polymorphic associations missing composite index", new: true}, counter_cache_issues: {name: "Counter Cache Issues", description: "counter_cache declarations whose target column is missing", new: true}, soft_delete_without_scope: {name: "Soft Delete Without Scope", description: "Soft-delete columns with no model scope filtering them", new: true}, orphan_tables: {name: "Orphan Tables", description: "DB tables without a corresponding Rails model", new: true} } } }.freeze
Class Method Summary collapse
- .all ⇒ Object
- .category(category) ⇒ Object
-
.documentation(report) ⇒ Object
Returns full documentation for a report including I18n translations.
- .find(category, report) ⇒ Object
-
.localized_category(cat_key, cat) ⇒ Object
Build category hash with localized name and report names.
-
.localized_report(rep_key, rep) ⇒ Object
Build report hash with localized name and description.
-
.problem_explanation(problem_key) ⇒ Object
Returns the problem explanation for a given problem type.
-
.problem_fields(report) ⇒ Object
Returns problem fields for a report.
-
.thresholds(report) ⇒ Object
Returns thresholds for a report.
Class Method Details
.all ⇒ Object
331 332 333 334 335 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 331 def self.all REPORTS.each_with_object({}) do |(cat_key, cat), result| result[cat_key] = localized_category(cat_key, cat) end end |
.category(category) ⇒ Object
344 345 346 347 348 349 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 344 def self.category(category) cat = REPORTS[category.to_sym] return nil unless cat localized_category(category.to_sym, cat) end |
.documentation(report) ⇒ Object
Returns full documentation for a report including I18n translations
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 370 def self.documentation(report) report_key = report.to_sym config = REPORT_CONFIG[report_key] || {thresholds: {}, problem_fields: []} # Get translations from I18n i18n_key = "pg_reports.documentation.#{report_key}" { title: I18n.t("#{i18n_key}.title", default: report.to_s.titleize), what: I18n.t("#{i18n_key}.what", default: ""), how: I18n.t("#{i18n_key}.how", default: ""), nuances: I18n.t("#{i18n_key}.nuances", default: []), ai_prompt: I18n.t("#{i18n_key}.ai_prompt", default: nil), thresholds: config[:thresholds], problem_fields: config[:problem_fields] } end |
.find(category, report) ⇒ Object
337 338 339 340 341 342 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 337 def self.find(category, report) rep = REPORTS.dig(category.to_sym, :reports, report.to_sym) return nil unless rep localized_report(report.to_sym, rep) end |
.localized_category(cat_key, cat) ⇒ Object
Build category hash with localized name and report names
352 353 354 355 356 357 358 359 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 352 def self.localized_category(cat_key, cat) cat.merge( name: I18n.t("pg_reports.categories.#{cat_key}", default: cat[:name]), reports: cat[:reports].each_with_object({}) do |(rep_key, rep), reports| reports[rep_key] = localized_report(rep_key, rep) end ) end |
.localized_report(rep_key, rep) ⇒ Object
Build report hash with localized name and description
362 363 364 365 366 367 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 362 def self.localized_report(rep_key, rep) rep.merge( name: I18n.t("pg_reports.reports.#{rep_key}.name", default: rep[:name]), description: I18n.t("pg_reports.reports.#{rep_key}.description", default: rep[:description]) ) end |
.problem_explanation(problem_key) ⇒ Object
Returns the problem explanation for a given problem type
388 389 390 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 388 def self.problem_explanation(problem_key) I18n.t("pg_reports.problems.#{problem_key}", default: "") end |
.problem_fields(report) ⇒ Object
Returns problem fields for a report
398 399 400 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 398 def self.problem_fields(report) REPORT_CONFIG.dig(report.to_sym, :problem_fields) || [] end |
.thresholds(report) ⇒ Object
Returns thresholds for a report
393 394 395 |
# File 'lib/pg_reports/dashboard/reports_registry.rb', line 393 def self.thresholds(report) REPORT_CONFIG.dig(report.to_sym, :thresholds) || {} end |