pg_reports 0.6.0 → 0.6.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +143 -378
- data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
- data/app/views/layouts/pg_reports/application.html.erb +65 -8
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +55 -57
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +18 -0
- data/app/views/pg_reports/dashboard/index.html.erb +109 -106
- data/app/views/pg_reports/dashboard/show.html.erb +26 -26
- data/config/locales/en.yml +488 -0
- data/config/locales/ru.yml +481 -0
- data/config/locales/uk.yml +481 -0
- data/lib/pg_reports/annotation_parser.rb +13 -1
- data/lib/pg_reports/compatibility.rb +3 -3
- data/lib/pg_reports/dashboard/reports_registry.rb +83 -12
- data/lib/pg_reports/definitions/schema_analysis/always_null_columns.yml +31 -0
- data/lib/pg_reports/definitions/schema_analysis/unused_columns.yml +32 -0
- data/lib/pg_reports/definitions/tables/unused_tables.yml +30 -0
- data/lib/pg_reports/definitions/tables/update_hotspots.yml +32 -0
- data/lib/pg_reports/module_generator.rb +2 -1
- data/lib/pg_reports/modules/schema_analysis.rb +261 -2
- data/lib/pg_reports/modules/system.rb +3 -3
- data/lib/pg_reports/query_monitor.rb +2 -6
- data/lib/pg_reports/report_definition.rb +20 -24
- data/lib/pg_reports/sql/schema_analysis/always_null_columns.sql +25 -0
- data/lib/pg_reports/sql/schema_analysis/unused_columns.sql +36 -0
- data/lib/pg_reports/sql/tables/unused_tables.sql +19 -0
- data/lib/pg_reports/sql/tables/update_hotspots.sql +26 -0
- data/lib/pg_reports/version.rb +1 -1
- metadata +9 -1
data/config/locales/en.yml
CHANGED
|
@@ -263,6 +263,31 @@ en:
|
|
|
263
263
|
- "High UPDATE activity creates dead tuples — monitor vacuum."
|
|
264
264
|
- "HOT updates (Heap Only Tuple) are more efficient — indexes are not updated."
|
|
265
265
|
|
|
266
|
+
update_hotspots:
|
|
267
|
+
title: "Update Hotspots"
|
|
268
|
+
what: "Tables where the same rows are updated repeatedly, or where indexed columns are being hot-updated."
|
|
269
|
+
how: "Computes two ratios from pg_stat_user_tables: updates_per_row = n_tup_upd / n_live_tup (signal for write amplification on individual rows) and hot_update_pct = n_tup_hot_upd / n_tup_upd (low value means an indexed column is in the SET clause, defeating HOT)."
|
|
270
|
+
nuances:
|
|
271
|
+
- "updates_per_row >> 1 means individual rows are being rewritten many times — common with counters, status flags, last_seen_at, position trackers."
|
|
272
|
+
- "Refactor patterns for hot rows: move the volatile column to a sibling 1:1 'state' table; use an event/log table with periodic rollups; batch writes via a background buffer; debounce updates application-side."
|
|
273
|
+
- "Low hot_update_pct (e.g. <50%) means indexed columns are being updated. Either drop the index or move that column to a less-indexed table."
|
|
274
|
+
- "PostgreSQL does not track per-column UPDATE counts. To find which column is the culprit, parse pg_stat_statements for UPDATE queries on the table and look at SET clauses (queries are normalized but column names are preserved)."
|
|
275
|
+
- "Hot rows + low fillfactor (default 100) eliminates HOT optimization. ALTER TABLE ... SET (fillfactor=80) leaves room for in-place updates."
|
|
276
|
+
- "High n_dead_tup correlates with hot rows — autovacuum must keep up or bloat will grow fast."
|
|
277
|
+
ai_prompt: "Reduce write amplification on these PostgreSQL tables. For each: identify the columns most commonly updated by parsing pg_stat_statements UPDATE queries, propose a refactor (split hot/cold columns into a sibling table, switch to an append-only event table, batch writes), and generate the migration. If hot_update_pct is low, identify the indexed column being updated and recommend either dropping the index or moving the column."
|
|
278
|
+
|
|
279
|
+
unused_tables:
|
|
280
|
+
title: "Unused Tables"
|
|
281
|
+
what: "Tables with zero seq_scan and zero idx_scan since the last statistics reset — application code never reads them."
|
|
282
|
+
how: "Filters pg_stat_user_tables for seq_scan = 0 AND idx_scan = 0. Cross-references db_stats_since (pg_stat_database.stats_reset) so you know how much history the verdict is based on."
|
|
283
|
+
nuances:
|
|
284
|
+
- "Verify db_stats_since covers a representative window — at minimum a full week, ideally a full reporting/billing cycle. A recent stats_reset, restart, or PG upgrade invalidates the result."
|
|
285
|
+
- "Tables read only by replicas are NOT counted on the primary. Check replicas separately before dropping."
|
|
286
|
+
- "Tables touched only by COPY, pg_dump, or logical replication slots may still appear unused — these don't increment scan counters."
|
|
287
|
+
- "Foreign keys referencing the table can keep it 'in use' through cascades even without direct queries."
|
|
288
|
+
- "Safe sequence: rename → wait one full cycle → drop. Or move to a separate archive schema first."
|
|
289
|
+
ai_prompt: "Archive these unread PostgreSQL tables. For each: verify the table is not referenced by FKs, code, or scheduled jobs, then generate a Rails migration that either drops the table or moves it to an `archive` schema. Add a rollback that restores the table. Recommend resetting pg_stat_user_tables before final removal to confirm zero reads over a fresh window."
|
|
290
|
+
|
|
266
291
|
# === CONNECTIONS ===
|
|
267
292
|
active_connections:
|
|
268
293
|
title: "Active Connections"
|
|
@@ -437,6 +462,79 @@ en:
|
|
|
437
462
|
ai_prompt: "Improve PostgreSQL cache hit ratio. Suggest specific shared_buffers and effective_cache_size tuning for postgresql.conf based on the current stats. Recommend application-level caching for frequently accessed data."
|
|
438
463
|
|
|
439
464
|
# === SCHEMA ANALYSIS ===
|
|
465
|
+
unused_columns:
|
|
466
|
+
title: "Unused Columns"
|
|
467
|
+
what: "Columns whose data has only ever held a single distinct value — strong sign nothing in the application updates them anymore."
|
|
468
|
+
how: "Reads pg_stats.n_distinct = 1 (only one value across all sampled rows) and joins with pg_attrdef to show the column default. Excludes primary keys and unique-index columns to avoid noise."
|
|
469
|
+
nuances:
|
|
470
|
+
- "n_distinct is a sample-based estimate — ANALYZE the table before trusting the result. Stale stats can produce false positives."
|
|
471
|
+
- "Tables under ~1000 rows are excluded because a single value across few rows is statistically meaningless."
|
|
472
|
+
- "False positives: feature flags, enum columns where only one value has shipped so far, columns defaulted in code with one valid option."
|
|
473
|
+
- "True positives: columns whose Ruby/ORM accessor was deleted but the migration to drop the column was never written; status fields replaced by a different mechanism but left in the schema."
|
|
474
|
+
- "PostgreSQL has no per-column write tracking, so this is a heuristic. Confirm by grepping the codebase for the column name before dropping."
|
|
475
|
+
- "Consider also `always_null_columns` — same diagnosis from a different angle."
|
|
476
|
+
ai_prompt: "Drop these unused PostgreSQL columns. For each column: search the application code for any reference to the column name (Ruby, SQL, fixtures, views), and only if zero references found generate a Rails migration that removes the column with a `safety_assured` block. Include the column type and default in the rollback."
|
|
477
|
+
|
|
478
|
+
always_null_columns:
|
|
479
|
+
title: "Always-NULL Columns"
|
|
480
|
+
what: "Nullable columns where 100% of rows are NULL — the application has stopped (or never started) writing them."
|
|
481
|
+
how: "Reads pg_stats.null_frac >= 0.999 (effectively all NULL) and excludes columns with a NOT NULL constraint. Joins pg_attrdef to surface any leftover column default."
|
|
482
|
+
nuances:
|
|
483
|
+
- "null_frac is sampled — run ANALYZE first if you doubt the value. Re-check after a representative workload window."
|
|
484
|
+
- "A column may be NULL because the writing code path is rarely hit (e.g. premium-only fields, opt-in features). Check before dropping."
|
|
485
|
+
- "If null_pct = 100 AND there is a non-NULL default, the default is unused — also worth removing alongside the column."
|
|
486
|
+
- "Some ORMs serialize empty strings as '' rather than NULL; this report won't catch always-empty columns. Add a separate report or query if you need that signal."
|
|
487
|
+
- "Compare with `unused_columns` — that report catches single-value columns; this one catches all-NULL columns. Together they cover the most common dead-column patterns."
|
|
488
|
+
ai_prompt: "Drop these always-NULL PostgreSQL columns. For each: confirm via codebase search that no writer references the column, then generate a Rails migration removing the column. If the rollback should restore data, note that there is none — the column was empty."
|
|
489
|
+
|
|
490
|
+
polymorphic_without_index:
|
|
491
|
+
title: "Polymorphic Without Index"
|
|
492
|
+
what: "Polymorphic `belongs_to` associations whose `(*_type, *_id)` pair has no composite index."
|
|
493
|
+
how: "Walks `ActiveRecord::Base.descendants`, collects `belongs_to` reflections with `polymorphic: true`, and checks `pg_index` for an index covering both the type and id columns."
|
|
494
|
+
nuances:
|
|
495
|
+
- "PostgreSQL cannot use two single-column indexes as efficiently as one composite index for `WHERE x_type = ? AND x_id = ?` lookups — this is the canonical fetch pattern for polymorphic associations."
|
|
496
|
+
- "Coverage hint clarifies the gap: \"neither indexed\" / \"only id indexed\" / \"only type indexed\" / \"type and id indexed separately\". The last two are partial fixes, not full ones."
|
|
497
|
+
- "On small association tables this is harmless; on large ones (Comment, Note, Activity, AuditLog) it turns every association load into a seq scan."
|
|
498
|
+
- "The suggested migration uses the `(type, id)` order — type usually has lower cardinality so the index tree narrows faster."
|
|
499
|
+
- "Eager-loads the application before walking models so dev environments produce a complete result."
|
|
500
|
+
ai_prompt: "Add composite indexes for these polymorphic associations. For each: generate a Rails migration with `add_index :<table>, [:<x>_type, :<x>_id]` using `algorithm: :concurrently` for large tables. Verify the existing single-column indexes (if any) can be dropped after the composite is in place."
|
|
501
|
+
|
|
502
|
+
counter_cache_issues:
|
|
503
|
+
title: "Counter Cache Issues"
|
|
504
|
+
what: "`belongs_to ..., counter_cache: ...` declarations whose target column is missing on the parent table."
|
|
505
|
+
how: "Walks all models, finds `belongs_to` with a `counter_cache` option, resolves the expected column name (`<child_table>_count` for `counter_cache: true`, or the explicit symbol/string), and checks the parent's columns."
|
|
506
|
+
nuances:
|
|
507
|
+
- "Missing column = the counter is silently broken: writes go nowhere, `parent.<assoc>_count` returns nil, and any code reading the cached value gets stale or zero."
|
|
508
|
+
- "Reverse direction (a `*_count` column with no `counter_cache` declaration pointing at it) is intentionally not flagged — too many false positives from manually maintained counters."
|
|
509
|
+
- "Resolves the parent class via `assoc.klass`; if the class is missing or fails to load, the row is skipped."
|
|
510
|
+
- "Default name for `counter_cache: true`: parent gets a column named after the **child** table pluralized + `_count`. Example: `Comment belongs_to :user, counter_cache: true` → `users.comments_count`."
|
|
511
|
+
- "Eager-loads the application before walking models so dev environments produce a complete result."
|
|
512
|
+
ai_prompt: "Fix these broken counter caches. For each row: generate a Rails migration that adds the missing column with `default: 0, null: false`, then a backfill task that runs `Parent.find_each { |p| Parent.reset_counters(p.id, :<assoc>) }`. Run the backfill before deploying any code that reads the counter."
|
|
513
|
+
|
|
514
|
+
soft_delete_without_scope:
|
|
515
|
+
title: "Soft Delete Without Scope"
|
|
516
|
+
what: "Tables with a `deleted_at` / `discarded_at` / `archived_at` column whose model has no scope filtering soft-deleted rows."
|
|
517
|
+
how: "For each table, checks if any of the canonical soft-delete column names exist. If so, finds the model and checks for `acts_as_paranoid` (paranoia gem), `discard_column` (discard gem), or a `default_scope` whose generated SQL references the column."
|
|
518
|
+
nuances:
|
|
519
|
+
- "Without a default scope, every plain `Model.where(...)` query returns soft-deleted rows. This silently leaks 'deleted' data into reports, indexes, search results, and exports."
|
|
520
|
+
- "If the team has decided **against** a default scope (because they prefer explicit `kept`/`with_deleted` calls everywhere), this report will produce one row per such model — those are intentional and can be ignored."
|
|
521
|
+
- "Detection method for default_scope: builds `model.all.to_sql` and looks for the column name. Catches `default_scope { where(deleted_at: nil) }` and equivalents but misses scopes added at runtime via concerns that aren't loaded yet."
|
|
522
|
+
- "Tables without a Rails model are reported separately with status `no_model` — usually means the table is queried only by raw SQL and needs manual review."
|
|
523
|
+
- "Eager-loads the application before walking models so dev environments produce a complete result."
|
|
524
|
+
ai_prompt: "Add soft-delete scopes to these Rails models. For each: add `default_scope { where(<column>: nil) }` and a `with_deleted` unscoped helper, OR adopt the `discard` gem (`include Discard::Model; self.discard_column = :<column>`). Verify no production code path expects to see soft-deleted rows by default."
|
|
525
|
+
|
|
526
|
+
orphan_tables:
|
|
527
|
+
title: "Orphan Tables"
|
|
528
|
+
what: "Database tables with no corresponding Rails model class."
|
|
529
|
+
how: "Lists all tables from `pg_class` (excluding `schema_migrations` and `ar_internal_metadata`) and tries common naming conventions to find a model. Tables that resolve to nothing are reported."
|
|
530
|
+
nuances:
|
|
531
|
+
- "Three classifications: `join_table_candidate` (exactly two `*_id` columns and nothing else — likely a legitimate HABTM table), `join_model_without_class` (multiple FK columns plus extra fields — was probably meant to be a join model), and `legacy` (everything else)."
|
|
532
|
+
- "`join_table_candidate` is usually intentional — Rails doesn't require a model for HABTM. Skim and ignore."
|
|
533
|
+
- "`legacy` rows are the interesting ones: tables created before the Rails model was added, renamed-but-not-dropped, created out-of-band by another service, or owned by a removed feature."
|
|
534
|
+
- "Cross-reference with `unused_tables` (Tables category) — a table that's both orphan AND has zero reads is a strong drop candidate."
|
|
535
|
+
- "False positives: namespaced models (`Admin::User` → `admin_users`), STI subclasses sharing a base table, models declared inside engines that aren't eager-loaded."
|
|
536
|
+
- "Row count is approximate (n_live_tup from pg_stat_user_tables) — newly written tables may show 0 until the next ANALYZE."
|
|
537
|
+
|
|
440
538
|
missing_validations:
|
|
441
539
|
title: "Missing Validations"
|
|
442
540
|
what: "Unique indexes in the database without corresponding uniqueness validations in Rails models."
|
|
@@ -475,3 +573,393 @@ en:
|
|
|
475
573
|
pool_saturation: "Connection pool is saturated. Risk of connection exhaustion and application errors."
|
|
476
574
|
high_connection_churn: "High connection churn rate. Implement connection pooling to reduce overhead."
|
|
477
575
|
too_many_short_connections: "Too many short-lived connections. Application should reuse connections via pooling."
|
|
576
|
+
unused_column: "Column has only one distinct value across all rows. Likely never updated since creation — candidate for removal."
|
|
577
|
+
always_null_column: "Column is 100% NULL. Application no longer writes to this field — candidate for removal."
|
|
578
|
+
hot_rows: "Same rows are updated repeatedly. Consider splitting hot/cold columns, batching writes, or using an event-log table."
|
|
579
|
+
low_hot_update: "HOT update rate is low — indexed columns are being updated. Drop the index or move the column to reduce write amplification."
|
|
580
|
+
unused_table: "Table has zero reads since the last stats reset. Candidate for archival or removal — verify the stats window first."
|
|
581
|
+
polymorphic_no_index: "Polymorphic association lacks a composite (type, id) index. Lookups will seq-scan as the table grows."
|
|
582
|
+
counter_cache_missing_column: "Counter cache column is missing on the parent table. The counter is silently broken — writes go nowhere."
|
|
583
|
+
soft_delete_unprotected: "Soft-delete column has no model scope filtering it. Plain queries return deleted rows."
|
|
584
|
+
orphan_table_legacy: "Table has no Rails model. Likely legacy — verify before dropping."
|
|
585
|
+
|
|
586
|
+
# UI strings shown in the dashboard chrome (buttons, modals, toasts, etc.)
|
|
587
|
+
ui:
|
|
588
|
+
branding:
|
|
589
|
+
title: "PgReports"
|
|
590
|
+
subtitle: "PostgreSQL Analysis Dashboard"
|
|
591
|
+
page_title: "PgReports Dashboard"
|
|
592
|
+
navigation:
|
|
593
|
+
dashboard: "Dashboard"
|
|
594
|
+
back: "← Back"
|
|
595
|
+
actions:
|
|
596
|
+
cancel: "Cancel"
|
|
597
|
+
retry: "Retry"
|
|
598
|
+
copy: "📋 Copy"
|
|
599
|
+
copy_query: "📋 Copy Query"
|
|
600
|
+
copy_code: "📋 Copy Code"
|
|
601
|
+
copy_to_clipboard_title: "Copy to clipboard"
|
|
602
|
+
copied_feedback: "✓ Copied!"
|
|
603
|
+
clear_all: "Clear All"
|
|
604
|
+
run_report: "▶ Run Report"
|
|
605
|
+
export: "⬇ Export"
|
|
606
|
+
download_text: "📄 Text (.txt)"
|
|
607
|
+
download_csv: "📊 CSV (.csv)"
|
|
608
|
+
download_json: "📋 JSON (.json)"
|
|
609
|
+
download: "📥 Download"
|
|
610
|
+
copy_ai_prompt: "Copy Prompt"
|
|
611
|
+
send_telegram: "📨 Telegram"
|
|
612
|
+
sending: "Sending..."
|
|
613
|
+
reset_statistics: "🗑️ Reset Statistics"
|
|
614
|
+
resetting: "Resetting..."
|
|
615
|
+
confirm_reset: "Yes, Reset"
|
|
616
|
+
create_extension: "⚡ Create Extension"
|
|
617
|
+
creating: "Creating..."
|
|
618
|
+
ide_settings_button_title: "IDE Settings"
|
|
619
|
+
explain_analyze: "📊 EXPLAIN ANALYZE"
|
|
620
|
+
execute_query: "▶ Execute Query"
|
|
621
|
+
create_migration_file: "📁 Create File & Open in IDE"
|
|
622
|
+
start_monitoring: "▶ Start Monitoring"
|
|
623
|
+
stop_monitoring: "⏹ Stop Monitoring"
|
|
624
|
+
starting: "Starting..."
|
|
625
|
+
stopping: "Stopping..."
|
|
626
|
+
load_history: "📜 Load History (50)"
|
|
627
|
+
loading: "Loading..."
|
|
628
|
+
running: "Running..."
|
|
629
|
+
save_for_comparison: "📌 Save for Comparison"
|
|
630
|
+
saved_marker: "📌 Saved"
|
|
631
|
+
status:
|
|
632
|
+
pg_stat_ready: "pg_stat_statements ready"
|
|
633
|
+
extension_installed: "Extension installed, not preloaded"
|
|
634
|
+
preloaded: "Preloaded, extension not created"
|
|
635
|
+
not_configured: "Not configured"
|
|
636
|
+
monitoring_unavailable: "Live Monitoring Unavailable"
|
|
637
|
+
modals:
|
|
638
|
+
enable_pg_stat_title: "Enable pg_stat_statements"
|
|
639
|
+
enable_pg_stat_intro: "To enable pg_stat_statements, follow these steps:"
|
|
640
|
+
edit_postgresql_conf: "Edit postgresql.conf:"
|
|
641
|
+
restart_postgresql: "Restart PostgreSQL:"
|
|
642
|
+
create_extension_step: "Create extension:"
|
|
643
|
+
enable_button_note: "Or click \"Enable\" button after restart."
|
|
644
|
+
reset_stats_title: "⚠️ Reset Statistics"
|
|
645
|
+
reset_stats_confirm: "Are you sure you want to reset pg_stat_statements statistics?"
|
|
646
|
+
reset_stats_warning: "This action will clear all collected query statistics and cannot be undone."
|
|
647
|
+
ide_settings_title: "⚙️ IDE Settings"
|
|
648
|
+
problem_detected_title: "⚠️ Problem Detected"
|
|
649
|
+
query_analyzer_title: "📊 Query Analyzer"
|
|
650
|
+
query_label: "Query:"
|
|
651
|
+
parameters_label: "Parameters:"
|
|
652
|
+
migration_title: "🗑️ Drop Index Migration"
|
|
653
|
+
migration_subtitle: "Generated migration to remove the index:"
|
|
654
|
+
migration_warning: "Creating a migration will generate a migration file in your project. Running this migration will drop the index from the database, which may significantly impact application performance."
|
|
655
|
+
migration_warning_dev_only: "This operation should only be performed in a local development environment."
|
|
656
|
+
query_execution_disabled_title: "⚠️ Query execution is disabled"
|
|
657
|
+
query_execution_disabled_intro: "To enable this feature, add to your configuration:"
|
|
658
|
+
settings:
|
|
659
|
+
default_ide_label: "Default IDE for source links:"
|
|
660
|
+
ide_show_menu: "Show menu (default)"
|
|
661
|
+
ide_vscode_wsl: "VS Code (WSL)"
|
|
662
|
+
ide_vscode: "VS Code"
|
|
663
|
+
ide_rubymine: "RubyMine"
|
|
664
|
+
ide_intellij: "IntelliJ IDEA"
|
|
665
|
+
ide_cursor_wsl: "Cursor (WSL)"
|
|
666
|
+
ide_cursor: "Cursor"
|
|
667
|
+
monitoring:
|
|
668
|
+
live_title: "Live Monitoring"
|
|
669
|
+
update_interval: "Updates every 5s"
|
|
670
|
+
toggle_title: "Toggle live monitoring"
|
|
671
|
+
query_monitor_title: "SQL Query Monitor"
|
|
672
|
+
session_label: "Session:"
|
|
673
|
+
queries_label: "Queries:"
|
|
674
|
+
feed_empty: "Click \"Start Monitoring\" to begin capturing SQL queries"
|
|
675
|
+
feed_no_queries: "No queries captured yet..."
|
|
676
|
+
unknown_source: "Unknown source"
|
|
677
|
+
expand_collapse_title: "Expand/Collapse"
|
|
678
|
+
metrics:
|
|
679
|
+
connections_label: "Connections"
|
|
680
|
+
tps_label: "TPS"
|
|
681
|
+
tps_unit: "tx/s"
|
|
682
|
+
commit_label: "commit:"
|
|
683
|
+
rollback_label: "rollback:"
|
|
684
|
+
cache_hit_label: "Cache Hit"
|
|
685
|
+
cache_hit_detail: "heap blocks from cache"
|
|
686
|
+
long_queries_label: "Long Queries"
|
|
687
|
+
queries_unit: "queries"
|
|
688
|
+
long_running_threshold: "> 60s runtime"
|
|
689
|
+
blocked_label: "Blocked"
|
|
690
|
+
processes_unit: "processes"
|
|
691
|
+
waiting_for_locks: "waiting for locks"
|
|
692
|
+
percent_used_suffix: "% used"
|
|
693
|
+
categories:
|
|
694
|
+
requires_pg_stat: "🔒 Requires pg_stat_statements"
|
|
695
|
+
reports_count_suffix: "reports"
|
|
696
|
+
documentation:
|
|
697
|
+
toggle_title: "📖 What does this report show?"
|
|
698
|
+
what_section: "📋 What"
|
|
699
|
+
why_section: "❓ Why It Matters"
|
|
700
|
+
nuances_section: "⚠️ Nuances"
|
|
701
|
+
thresholds_section: "📊 Thresholds"
|
|
702
|
+
threshold_warning_label: "⚠️ Warning:"
|
|
703
|
+
threshold_critical_label: "🔴 Critical:"
|
|
704
|
+
threshold_inverted_note: "(lower is worse)"
|
|
705
|
+
filters:
|
|
706
|
+
title: "🔍 Filter Parameters"
|
|
707
|
+
current_value: "current"
|
|
708
|
+
saved:
|
|
709
|
+
title: "📌 Saved for Comparison"
|
|
710
|
+
saved_at_prefix: "▸ Saved:"
|
|
711
|
+
click_to_expand: "Click to expand"
|
|
712
|
+
confirm_clear_all: "Remove all saved records for this report?"
|
|
713
|
+
remove_title: "Remove"
|
|
714
|
+
results:
|
|
715
|
+
title: "Results"
|
|
716
|
+
click_run_hint: "Click \"Run Report\" to fetch data"
|
|
717
|
+
empty_message: "No issues found. Everything looks good!"
|
|
718
|
+
showing_first_of_total: "Showing first %{count} of %{total} rows"
|
|
719
|
+
no_rows_returned: "No rows returned"
|
|
720
|
+
rows_label: "Rows:"
|
|
721
|
+
execution_time_label: "Execution time:"
|
|
722
|
+
null_placeholder: "<null>"
|
|
723
|
+
sections:
|
|
724
|
+
recommendation: "💡 Recommendation"
|
|
725
|
+
detected_issues: "⚠️ Detected Issues"
|
|
726
|
+
execution_plan: "📊 Execution Plan"
|
|
727
|
+
line_label: "Line"
|
|
728
|
+
current_label: "Current:"
|
|
729
|
+
threshold_label: "Threshold:"
|
|
730
|
+
threshold_inverted_long: "(inverted: lower values are worse)"
|
|
731
|
+
warning_eq: "warning"
|
|
732
|
+
critical_eq: "critical"
|
|
733
|
+
levels:
|
|
734
|
+
critical: "🔴 Critical"
|
|
735
|
+
warning: "⚠️ Warning"
|
|
736
|
+
errors:
|
|
737
|
+
error_prefix: "Error:"
|
|
738
|
+
unable_fetch_metrics: "Unable to fetch database statistics."
|
|
739
|
+
possible_causes: "This may be due to:"
|
|
740
|
+
cause_permissions: "Insufficient database permissions"
|
|
741
|
+
cause_views: "Database statistics views not accessible"
|
|
742
|
+
cause_connection: "Connection issues"
|
|
743
|
+
fetch_metrics_failed: "Failed to fetch live metrics"
|
|
744
|
+
fetch_metrics_check_perms: "Unable to fetch database statistics. Check database permissions."
|
|
745
|
+
insufficient_database_perms: "Insufficient database permissions to access statistics views"
|
|
746
|
+
network_error_prefix: "Network error:"
|
|
747
|
+
copy_failed: "Failed to copy"
|
|
748
|
+
run_report_first: "Run the report first"
|
|
749
|
+
run_report_failed: "Failed to run report"
|
|
750
|
+
report_not_found: "Report not found"
|
|
751
|
+
send_telegram_failed: "Failed to send to Telegram"
|
|
752
|
+
reset_stats_failed: "Failed to reset statistics"
|
|
753
|
+
start_monitoring_failed: "Failed to start monitoring"
|
|
754
|
+
stop_monitoring_failed: "Failed to stop monitoring"
|
|
755
|
+
load_history_failed: "Failed to load history:"
|
|
756
|
+
no_query_history: "No query history found"
|
|
757
|
+
decode_query_failed: "Failed to decode query"
|
|
758
|
+
explain_analyze_failed: "Failed to run EXPLAIN ANALYZE"
|
|
759
|
+
execute_query_failed: "Failed to execute query"
|
|
760
|
+
create_migration_failed: "Failed to create migration"
|
|
761
|
+
explain_disabled_toast: "⚠️ EXPLAIN ANALYZE is disabled. Enable in configuration: config.allow_raw_query_execution = true"
|
|
762
|
+
execute_disabled_toast: "⚠️ Query execution is disabled. Enable in configuration: config.allow_raw_query_execution = true"
|
|
763
|
+
query_monitoring_error: "Query monitoring error"
|
|
764
|
+
query_hash_required: "Query hash is required"
|
|
765
|
+
query_execution_disabled: "Query execution from dashboard is disabled. Enable it in configuration with 'config.allow_raw_query_execution = true'"
|
|
766
|
+
query_not_found_expired: "Query not found or expired. Please refresh the page."
|
|
767
|
+
security_violation_prefix: "Security violation:"
|
|
768
|
+
trigger_variables_not_allowed: "Cannot EXPLAIN ANALYZE queries with trigger variables (NEW, OLD). These are only available within trigger functions."
|
|
769
|
+
missing_parameter_values: "Please provide values for all parameter placeholders ($1, $2, etc.)"
|
|
770
|
+
migration_dev_only: "Migration creation is only allowed in development environment"
|
|
771
|
+
filename_code_required: "File name and code are required"
|
|
772
|
+
invalid_filename_format: "Invalid migration file name format"
|
|
773
|
+
migrations_dir_not_found: "Migrations directory not found"
|
|
774
|
+
success:
|
|
775
|
+
statistics_reset: "Statistics have been reset successfully"
|
|
776
|
+
report_generated: "Report generated successfully"
|
|
777
|
+
ai_prompt_copied: "AI prompt copied to clipboard"
|
|
778
|
+
explain_copied: "EXPLAIN output copied to clipboard"
|
|
779
|
+
migration_copied: "Migration code copied to clipboard"
|
|
780
|
+
migration_created: "Migration created successfully"
|
|
781
|
+
telegram_sent: "Report sent to Telegram"
|
|
782
|
+
queries_loaded: "Loaded %{count} queries from history"
|
|
783
|
+
record_saved: "Record saved for comparison"
|
|
784
|
+
record_removed: "Record removed"
|
|
785
|
+
record_removed_saved: "Record removed from saved"
|
|
786
|
+
all_saved_cleared: "All saved records cleared"
|
|
787
|
+
|
|
788
|
+
# Category names (shown on the dashboard grid)
|
|
789
|
+
categories:
|
|
790
|
+
queries: "Queries"
|
|
791
|
+
indexes: "Indexes"
|
|
792
|
+
tables: "Tables"
|
|
793
|
+
connections: "Connections"
|
|
794
|
+
system: "System"
|
|
795
|
+
schema_analysis: "Schema Analysis"
|
|
796
|
+
|
|
797
|
+
# Report names and short descriptions (shown on the dashboard listing)
|
|
798
|
+
reports:
|
|
799
|
+
slow_queries:
|
|
800
|
+
name: "Slow Queries"
|
|
801
|
+
description: "Queries with high mean execution time"
|
|
802
|
+
heavy_queries:
|
|
803
|
+
name: "Heavy Queries"
|
|
804
|
+
description: "Most frequently called queries"
|
|
805
|
+
expensive_queries:
|
|
806
|
+
name: "Expensive Queries"
|
|
807
|
+
description: "Queries consuming most total time"
|
|
808
|
+
missing_index_queries:
|
|
809
|
+
name: "Missing Index Queries"
|
|
810
|
+
description: "Queries potentially missing indexes"
|
|
811
|
+
low_cache_hit_queries:
|
|
812
|
+
name: "Low Cache Hit"
|
|
813
|
+
description: "Queries with poor cache utilization"
|
|
814
|
+
temp_file_queries:
|
|
815
|
+
name: "Temp File Queries"
|
|
816
|
+
description: "Queries spilling to disk"
|
|
817
|
+
all_queries:
|
|
818
|
+
name: "All Queries"
|
|
819
|
+
description: "All query statistics"
|
|
820
|
+
unused_indexes:
|
|
821
|
+
name: "Unused Indexes"
|
|
822
|
+
description: "Indexes rarely or never scanned"
|
|
823
|
+
duplicate_indexes:
|
|
824
|
+
name: "Duplicate Indexes"
|
|
825
|
+
description: "Redundant indexes"
|
|
826
|
+
invalid_indexes:
|
|
827
|
+
name: "Invalid Indexes"
|
|
828
|
+
description: "Indexes that failed to build"
|
|
829
|
+
missing_indexes:
|
|
830
|
+
name: "Missing Indexes"
|
|
831
|
+
description: "Tables potentially missing indexes"
|
|
832
|
+
inefficient_indexes:
|
|
833
|
+
name: "Inefficient Indexes"
|
|
834
|
+
description: "Indexes with high read-to-fetch ratio"
|
|
835
|
+
index_usage:
|
|
836
|
+
name: "Index Usage"
|
|
837
|
+
description: "Index scan statistics"
|
|
838
|
+
bloated_indexes:
|
|
839
|
+
name: "Bloated Indexes"
|
|
840
|
+
description: "Indexes with high bloat"
|
|
841
|
+
fk_without_indexes:
|
|
842
|
+
name: "FK Without Indexes"
|
|
843
|
+
description: "Foreign keys missing indexes"
|
|
844
|
+
index_correlation:
|
|
845
|
+
name: "Index Correlation"
|
|
846
|
+
description: "Low physical correlation indexes"
|
|
847
|
+
index_sizes:
|
|
848
|
+
name: "Index Sizes"
|
|
849
|
+
description: "Index disk usage"
|
|
850
|
+
table_sizes:
|
|
851
|
+
name: "Table Sizes"
|
|
852
|
+
description: "Table disk usage"
|
|
853
|
+
bloated_tables:
|
|
854
|
+
name: "Bloated Tables"
|
|
855
|
+
description: "Tables with high dead tuple ratio"
|
|
856
|
+
vacuum_needed:
|
|
857
|
+
name: "Vacuum Needed"
|
|
858
|
+
description: "Tables needing vacuum"
|
|
859
|
+
row_counts:
|
|
860
|
+
name: "Row Counts"
|
|
861
|
+
description: "Table row counts"
|
|
862
|
+
cache_hit_ratios:
|
|
863
|
+
name: "Cache Hit Ratios"
|
|
864
|
+
description: "Table cache statistics"
|
|
865
|
+
seq_scans:
|
|
866
|
+
name: "Sequential Scans"
|
|
867
|
+
description: "Tables with high sequential scans"
|
|
868
|
+
tables_without_pk:
|
|
869
|
+
name: "No Primary Key"
|
|
870
|
+
description: "Tables missing primary keys"
|
|
871
|
+
recently_modified:
|
|
872
|
+
name: "Recently Modified"
|
|
873
|
+
description: "Tables with recent activity"
|
|
874
|
+
update_hotspots:
|
|
875
|
+
name: "Update Hotspots"
|
|
876
|
+
description: "Same rows or indexed columns updated repeatedly"
|
|
877
|
+
unused_tables:
|
|
878
|
+
name: "Unused Tables"
|
|
879
|
+
description: "Tables never queried since the last stats reset"
|
|
880
|
+
active_connections:
|
|
881
|
+
name: "Active Connections"
|
|
882
|
+
description: "Current database connections"
|
|
883
|
+
connection_stats:
|
|
884
|
+
name: "Connection Stats"
|
|
885
|
+
description: "Connection statistics by state"
|
|
886
|
+
long_running_queries:
|
|
887
|
+
name: "Long Running"
|
|
888
|
+
description: "Queries running for extended period"
|
|
889
|
+
blocking_queries:
|
|
890
|
+
name: "Blocking Queries"
|
|
891
|
+
description: "Queries blocking others"
|
|
892
|
+
locks:
|
|
893
|
+
name: "Locks"
|
|
894
|
+
description: "Current database locks"
|
|
895
|
+
idle_connections:
|
|
896
|
+
name: "Idle Connections"
|
|
897
|
+
description: "Idle connections"
|
|
898
|
+
pool_usage:
|
|
899
|
+
name: "Pool Usage"
|
|
900
|
+
description: "Connection pool utilization"
|
|
901
|
+
pool_wait_times:
|
|
902
|
+
name: "Wait Times"
|
|
903
|
+
description: "Resource wait analysis"
|
|
904
|
+
pool_saturation:
|
|
905
|
+
name: "Pool Saturation"
|
|
906
|
+
description: "Pool health warnings"
|
|
907
|
+
connection_churn:
|
|
908
|
+
name: "Connection Churn"
|
|
909
|
+
description: "Connection lifecycle analysis"
|
|
910
|
+
database_sizes:
|
|
911
|
+
name: "Database Sizes"
|
|
912
|
+
description: "Size of all databases"
|
|
913
|
+
settings:
|
|
914
|
+
name: "Settings"
|
|
915
|
+
description: "PostgreSQL configuration"
|
|
916
|
+
extensions:
|
|
917
|
+
name: "Extensions"
|
|
918
|
+
description: "Installed extensions"
|
|
919
|
+
activity_overview:
|
|
920
|
+
name: "Activity Overview"
|
|
921
|
+
description: "Current activity summary"
|
|
922
|
+
wraparound_risk:
|
|
923
|
+
name: "Wraparound Risk"
|
|
924
|
+
description: "Transaction ID wraparound proximity"
|
|
925
|
+
checkpoint_stats:
|
|
926
|
+
name: "Checkpoint Stats"
|
|
927
|
+
description: "Checkpoint and bgwriter statistics"
|
|
928
|
+
cache_stats:
|
|
929
|
+
name: "Cache Stats"
|
|
930
|
+
description: "Database cache statistics"
|
|
931
|
+
missing_validations:
|
|
932
|
+
name: "Missing Validations"
|
|
933
|
+
description: "Unique indexes without model validations"
|
|
934
|
+
unused_columns:
|
|
935
|
+
name: "Unused Columns"
|
|
936
|
+
description: "Columns that have only ever held a single value"
|
|
937
|
+
always_null_columns:
|
|
938
|
+
name: "Always-NULL Columns"
|
|
939
|
+
description: "Nullable columns that contain only NULL"
|
|
940
|
+
polymorphic_without_index:
|
|
941
|
+
name: "Polymorphic Without Index"
|
|
942
|
+
description: "Polymorphic associations missing composite index"
|
|
943
|
+
counter_cache_issues:
|
|
944
|
+
name: "Counter Cache Issues"
|
|
945
|
+
description: "counter_cache declarations whose target column is missing"
|
|
946
|
+
soft_delete_without_scope:
|
|
947
|
+
name: "Soft Delete Without Scope"
|
|
948
|
+
description: "Soft-delete columns with no model scope filtering them"
|
|
949
|
+
orphan_tables:
|
|
950
|
+
name: "Orphan Tables"
|
|
951
|
+
description: "DB tables without a corresponding Rails model"
|
|
952
|
+
|
|
953
|
+
# Filter parameter labels and descriptions
|
|
954
|
+
parameters:
|
|
955
|
+
limit:
|
|
956
|
+
label: "Limit"
|
|
957
|
+
description: "Maximum number of results"
|
|
958
|
+
min_calls:
|
|
959
|
+
label: "Min Calls"
|
|
960
|
+
description: "Minimum number of query calls"
|
|
961
|
+
min_duration_seconds:
|
|
962
|
+
label: "Min Duration (seconds)"
|
|
963
|
+
description: "Minimum query duration in seconds"
|
|
964
|
+
threshold_label: "%{field} Threshold"
|
|
965
|
+
threshold_description: "Override threshold for %{field}"
|