pg_reports 0.1.0 → 0.2.1

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.
@@ -0,0 +1,310 @@
1
+ uk:
2
+ pg_reports:
3
+ documentation:
4
+ # === QUERIES ===
5
+ slow_queries:
6
+ title: "Slow Queries"
7
+ what: "Запити з високим середнім часом виконання (mean_time)."
8
+ how: "Дані збираються з pg_stat_statements — розширення PostgreSQL, яке відстежує статистику виконання всіх SQL-запитів. Середній час розраховується як total_time / calls."
9
+ nuances:
10
+ - "Планувальник PostgreSQL може НЕ використовувати індекси для невеликих таблиць (< 10-50 тис. рядків), якщо sequential scan швидший — це нормальна поведінка."
11
+ - "Високий середній час може бути спричинений: відсутністю індексу, неоптимальним планом, блокуваннями, навантаженням на диск."
12
+ - "Для комплексних індексів важливий порядок колонок — індекс (a, b) ефективний для WHERE a = ? та WHERE a = ? AND b = ?, але не для WHERE b = ?."
13
+ - "Запити з LIKE '%pattern%' не можуть використовувати B-tree індекси — розгляньте pg_trgm або повнотекстовий пошук."
14
+
15
+ heavy_queries:
16
+ title: "Heavy Queries"
17
+ what: "Найчастіше викликані запити (за кількістю викликів)."
18
+ how: "Підраховується поле calls з pg_stat_statements. Висока кількість викликів збільшує загальне навантаження на базу, навіть якщо кожен окремий запит швидкий."
19
+ nuances:
20
+ - "Часті запити — перші кандидати на кешування на рівні застосунку (Redis, Memcached)."
21
+ - "N+1 проблема: якщо бачите багато схожих запитів з різними ID — це ознака N+1, потрібен eager loading."
22
+ - "Розгляньте batch-операції замість множини одиночних запитів."
23
+ - "Prepared statements можуть знизити накладні витрати на парсинг для частих запитів."
24
+
25
+ expensive_queries:
26
+ title: "Expensive Queries"
27
+ what: "Запити, що споживають найбільше сумарного часу (total_time = mean_time × calls)."
28
+ how: "Сортування за total_time показує запити з найбільшим впливом на продуктивність бази в цілому."
29
+ nuances:
30
+ - "Запит може бути швидким, але викликатися так часто, що сумарно займає багато часу."
31
+ - "Оптимізація expensive queries дає найбільший ефект для загальної продуктивності системи."
32
+ - "Розгляньте: додавання індексів, переписування запиту, кешування результатів, матеріалізовані представлення."
33
+
34
+ missing_index_queries:
35
+ title: "Missing Index Queries"
36
+ what: "Запити, що виконують sequential scan на великих таблицях замість використання індексів."
37
+ how: "Аналізується кількість seq_scan та seq_tup_read для таблиць, задіяних у запитах. Велика кількість прочитаних рядків при seq_scan вказує на потенційну необхідність індексу."
38
+ nuances:
39
+ - "Sequential scan НОРМАЛЬНИЙ для: маленьких таблиць, запитів що повертають >5-10% даних, таблиць без відповідних колонок для індексації."
40
+ - "Комплексний індекс (a, b, c) покриває: WHERE a, WHERE a AND b, WHERE a AND b AND c, але НЕ покриває WHERE b або WHERE c."
41
+ - "EXPLAIN ANALYZE покаже реальний план виконання запиту."
42
+ - "Partial indexes (CREATE INDEX ... WHERE condition) економлять місце та прискорюють специфічні запити."
43
+
44
+ low_cache_hit_queries:
45
+ title: "Low Cache Hit Queries"
46
+ what: "Запити з низьким відсотком попадання в кеш (shared_buffers)."
47
+ how: "Розраховується як shared_blks_hit / (shared_blks_hit + shared_blks_read). Низький cache hit ratio означає часте читання з диска."
48
+ nuances:
49
+ - "Цільовий cache hit ratio: >95% для активних даних."
50
+ - "Низький cache hit може вказувати на: недостатній shared_buffers, занадто великі таблиці, неефективні запити що сканують багато даних."
51
+ - "Збільшення shared_buffers допомагає, але є межа ефективності (зазвичай 25% RAM)."
52
+ - "Covering indexes (INCLUDE) дозволяють виконувати index-only scan без звернення до таблиці."
53
+
54
+ all_queries:
55
+ title: "All Queries"
56
+ what: "Повна статистика по всіх запитах з pg_stat_statements."
57
+ how: "Відображає всі відстежувані запити з їх метриками: calls, total_time, mean_time, rows тощо."
58
+ nuances:
59
+ - "pg_stat_statements нормалізує запити, замінюючи літерали на $1, $2 тощо."
60
+ - "Статистика скидається при: перезапуску PostgreSQL, виклику pg_stat_statements_reset(), досягненні ліміту track_activity_query_size."
61
+ - "Параметр pg_stat_statements.max контролює максимальну кількість відстежуваних запитів (за замовчуванням 5000)."
62
+
63
+ # === INDEXES ===
64
+ unused_indexes:
65
+ title: "Unused Indexes"
66
+ what: "Індекси, які рідко або ніколи не використовуються для сканування."
67
+ how: "Аналізується idx_scan з pg_stat_user_indexes. Індекси з нульовою або дуже низькою кількістю сканувань — кандидати на видалення."
68
+ nuances:
69
+ - "Перед видаленням переконайтеся, що статистика зібрана за достатній період (включаючи всі типи навантаження: звіти, batch-операції)."
70
+ - "Індекси все одно оновлюються при INSERT/UPDATE/DELETE, навіть якщо не використовуються для читання — це overhead."
71
+ - "Унікальні індекси (UNIQUE) можуть мати 0 сканувань, але потрібні для constraint."
72
+ - "Foreign key індекси важливі для продуктивності DELETE/UPDATE батьківських таблиць."
73
+
74
+ duplicate_indexes:
75
+ title: "Duplicate Indexes"
76
+ what: "Індекси, які дублюють інші індекси (один є префіксом іншого)."
77
+ how: "Порівнюються визначення індексів. Індекс (a) надлишковий при наявності (a, b). Також виявляються повні дублікати."
78
+ nuances:
79
+ - "Дублікати витрачають дисковий простір та сповільнюють INSERT/UPDATE/DELETE."
80
+ - "Однак іноді 'дублікат' потрібен: різні типи індексів (B-tree vs GIN), partial index vs full index."
81
+ - "Індекс (a, b) НЕ повністю замінює (b, a) — порядок колонок критичний."
82
+
83
+ invalid_indexes:
84
+ title: "Invalid Indexes"
85
+ what: "Індекси, створення яких не завершилося успішно (invalid state)."
86
+ how: "Перевіряється прапор indisvalid в pg_index. False означає, що індекс не буде використовуватися планувальником."
87
+ nuances:
88
+ - "Зазвичай з'являються після невдалого CREATE INDEX CONCURRENTLY."
89
+ - "Рішення: DROP INDEX та повторне створення."
90
+ - "REINDEX CONCURRENTLY може допомогти для існуючих невалідних індексів (PostgreSQL 12+)."
91
+
92
+ missing_indexes:
93
+ title: "Missing Indexes"
94
+ what: "Таблиці з високою кількістю sequential scan, яким, ймовірно, не вистачає індексів."
95
+ how: "Аналізується співвідношення seq_scan vs idx_scan та кількість прочитаних рядків (seq_tup_read)."
96
+ nuances:
97
+ - "Не всі seq_scan погані — для маленьких таблиць це оптимально."
98
+ - "Аналізуйте конкретні запити через pg_stat_statements, щоб зрозуміти які колонки індексувати."
99
+ - "Використовуйте EXPLAIN ANALYZE для перевірки, чи буде індекс використовуватися."
100
+
101
+ index_usage:
102
+ title: "Index Usage"
103
+ what: "Статистика використання індексів: скільки разів сканувалися, скільки рядків повертали."
104
+ how: "Дані з pg_stat_user_indexes показують ефективність кожного індексу."
105
+ nuances:
106
+ - "idx_scan — кількість index scan операцій."
107
+ - "idx_tup_read — рядки прочитані з індексу."
108
+ - "idx_tup_fetch — рядки отримані з таблиці після index scan (для non-covering indexes)."
109
+
110
+ bloated_indexes:
111
+ title: "Bloated Indexes"
112
+ what: "Індекси з високим рівнем bloat (роздування) через мертві кортежі."
113
+ how: "Оцінюється відношення реального розміру до очікуваного. Bloat з'являється після UPDATE/DELETE операцій."
114
+ nuances:
115
+ - "REINDEX або REINDEX CONCURRENTLY перестворює індекс без bloat."
116
+ - "Регулярний VACUUM підтримує bloat на прийнятному рівні."
117
+ - "pg_repack дозволяє перебудувати індекси без блокувань."
118
+ - "Bloat >30-50% — привід для дій."
119
+
120
+ index_sizes:
121
+ title: "Index Sizes"
122
+ what: "Розміри індексів на диску."
123
+ how: "Показує pg_relation_size для кожного індексу."
124
+ nuances:
125
+ - "Великі індекси потребують більше пам'яті для ефективного кешування."
126
+ - "Partial indexes можуть значно зменшити розмір: CREATE INDEX ... WHERE active = true."
127
+ - "BRIN індекси набагато компактніші за B-tree для деяких патернів даних (наприклад, timestamp колонки)."
128
+
129
+ # === TABLES ===
130
+ table_sizes:
131
+ title: "Table Sizes"
132
+ what: "Розміри таблиць на диску (дані + TOAST + індекси)."
133
+ how: "Використовує pg_total_relation_size для повного розміру включаючи всі пов'язані об'єкти."
134
+ nuances:
135
+ - "TOAST зберігає великі значення (>2KB) в окремій таблиці."
136
+ - "Партиціонування допомагає керувати великими таблицями."
137
+ - "Регулярний VACUUM FULL може зменшити розмір, але блокує таблицю."
138
+
139
+ bloated_tables:
140
+ title: "Bloated Tables"
141
+ what: "Таблиці з високим відсотком dead tuples (мертвих рядків)."
142
+ how: "Аналізується n_dead_tup з pg_stat_user_tables. Dead tuples з'являються після UPDATE/DELETE."
143
+ nuances:
144
+ - "VACUUM видаляє dead tuples та звільняє місце для повторного використання."
145
+ - "VACUUM FULL фізично зменшує розмір файлу, але блокує таблицю."
146
+ - "autovacuum повинен справлятися автоматично — перевірте його налаштування якщо бачите багато bloat."
147
+ - "Dead tuple ratio >20% — сигнал проблеми з vacuum."
148
+
149
+ vacuum_needed:
150
+ title: "Vacuum Needed"
151
+ what: "Таблиці, яким потрібен VACUUM на основі статистики dead tuples та часу останнього vacuum."
152
+ how: "Комбінує дані про dead tuples, останній vacuum та розмір таблиці."
153
+ nuances:
154
+ - "last_vacuum та last_autovacuum показують коли останній раз запускався vacuum."
155
+ - "Якщо autovacuum не справляється, можна: знизити autovacuum_vacuum_threshold, збільшити autovacuum_vacuum_scale_factor, додати workers."
156
+ - "Для критичних таблиць можна налаштувати індивідуальні параметри: ALTER TABLE SET (autovacuum_vacuum_threshold = 1000)."
157
+
158
+ row_counts:
159
+ title: "Row Counts"
160
+ what: "Приблизна кількість рядків у таблицях."
161
+ how: "Використовує n_live_tup з pg_stat_user_tables (оновлюється ANALYZE) або reltuples з pg_class."
162
+ nuances:
163
+ - "Це приблизна оцінка, точний COUNT(*) може відрізнятися."
164
+ - "ANALYZE оновлює статистику, autovacuum запускає його автоматично."
165
+ - "Для точного підрахунку використовуйте SELECT COUNT(*), але це може бути повільно на великих таблицях."
166
+
167
+ cache_hit_ratios:
168
+ title: "Cache Hit Ratios"
169
+ what: "Відсоток попадання в кеш (shared buffers) для кожної таблиці."
170
+ how: "Розраховується як heap_blks_hit / (heap_blks_hit + heap_blks_read)."
171
+ nuances:
172
+ - "Цільове значення: >95% для активно використовуваних таблиць."
173
+ - "Низький cache hit: таблиця занадто велика для кешу або рідко використовується."
174
+ - "Рішення: збільшити shared_buffers, оптимізувати запити, додати індекси для зменшення сканованих даних."
175
+
176
+ seq_scans:
177
+ title: "Sequential Scans"
178
+ what: "Таблиці з високою кількістю sequential scan операцій."
179
+ how: "Дані з pg_stat_user_tables показують seq_scan vs idx_scan."
180
+ nuances:
181
+ - "seq_scan не завжди погано — для маленьких таблиць це оптимально."
182
+ - "seq_tup_read показує скільки рядків прочитано — це важливіше ніж кількість scan."
183
+ - "Високий seq_tup_read/seq_scan = багато рядків за один scan = можливо норма."
184
+ - "Низький seq_tup_read/seq_scan = багато scan маленьких об'ємів = можливо N+1."
185
+
186
+ recently_modified:
187
+ title: "Recently Modified"
188
+ what: "Таблиці з недавньою активністю INSERT/UPDATE/DELETE."
189
+ how: "Аналізуються n_tup_ins, n_tup_upd, n_tup_del з pg_stat_user_tables."
190
+ nuances:
191
+ - "Допомагає зрозуміти патерни запису в базу."
192
+ - "Висока активність UPDATE створює dead tuples — слідкуйте за vacuum."
193
+ - "HOT updates (Heap Only Tuple) ефективніші за звичайні — індекси не оновлюються."
194
+
195
+ # === CONNECTIONS ===
196
+ active_connections:
197
+ title: "Active Connections"
198
+ what: "Поточні підключення до бази даних."
199
+ how: "Дані з pg_stat_activity показують всі активні backend процеси."
200
+ nuances:
201
+ - "Кожне підключення споживає пам'ять (~10MB per connection)."
202
+ - "max_connections обмежує загальну кількість — не встановлюйте занадто високо."
203
+ - "Використовуйте connection pooler (PgBouncer, pgpool-II) для масштабування."
204
+ - "state: active = виконує запит, idle = чекає, idle in transaction = небезпечно."
205
+
206
+ connection_stats:
207
+ title: "Connection Stats"
208
+ what: "Статистика підключень згрупована за станом (state)."
209
+ how: "Агрегація pg_stat_activity за полем state."
210
+ nuances:
211
+ - "idle in transaction: транзакція відкрита але не активна — блокує VACUUM, утримує локи."
212
+ - "idle in transaction (aborted): помилка в транзакції, чекає ROLLBACK."
213
+ - "Налаштуйте idle_in_transaction_session_timeout для автоматичного завершення."
214
+
215
+ long_running_queries:
216
+ title: "Long Running Queries"
217
+ what: "Запити, що виконуються довше заданого порогу."
218
+ how: "Фільтрація pg_stat_activity за duration = now() - query_start."
219
+ nuances:
220
+ - "Довгі запити можуть тримати блокування та заважати іншим процесам."
221
+ - "statement_timeout може автоматично переривати занадто довгі запити."
222
+ - "Для звітів/аналітики використовуйте read replica або налаштуйте окремий connection pool."
223
+
224
+ blocking_queries:
225
+ title: "Blocking Queries"
226
+ what: "Запити, які блокують виконання інших запитів."
227
+ how: "Аналіз pg_locks для пошуку ланцюжків блокувань."
228
+ nuances:
229
+ - "Блокування — нормальна частина роботи, але довгі блокування проблематичні."
230
+ - "Часті причини: довгі транзакції, відсутні індекси при UPDATE/DELETE по FK."
231
+ - "pg_cancel_backend(pid) скасовує запит, pg_terminate_backend(pid) завершує з'єднання."
232
+ - "Розгляньте оптимістичне блокування на рівні застосунку."
233
+
234
+ locks:
235
+ title: "Locks"
236
+ what: "Поточні блокування в базі даних."
237
+ how: "Дані з pg_locks показують всі активні блокування всіх типів."
238
+ nuances:
239
+ - "Типи: AccessShare (SELECT), RowShare (SELECT FOR UPDATE), RowExclusive (INSERT/UPDATE/DELETE), AccessExclusive (ALTER TABLE, DROP)."
240
+ - "granted = false означає, що запит чекає на блокування."
241
+ - "deadlock_timeout контролює через скільки PostgreSQL перевірить на deadlock."
242
+
243
+ idle_connections:
244
+ title: "Idle Connections"
245
+ what: "Підключення в стані idle (не виконують запити)."
246
+ how: "Фільтрація pg_stat_activity за state = 'idle'."
247
+ nuances:
248
+ - "Помірна кількість idle з'єднань — норма для connection pooling."
249
+ - "Занадто багато idle = застосунок не закриває з'єднання або пул занадто великий."
250
+ - "idle_session_timeout (PostgreSQL 14+) може автоматично закривати idle з'єднання."
251
+
252
+ # === SYSTEM ===
253
+ database_sizes:
254
+ title: "Database Sizes"
255
+ what: "Розміри всіх баз даних на сервері."
256
+ how: "Використовує pg_database_size() для кожної бази."
257
+ nuances:
258
+ - "Розмір включає всі таблиці, індекси, TOAST та тимчасові об'єкти."
259
+ - "Фізичне видалення даних не одразу зменшує розмір — потрібен VACUUM FULL."
260
+ - "Моніторте зростання для планування дискового простору."
261
+
262
+ settings:
263
+ title: "PostgreSQL Settings"
264
+ what: "Поточні налаштування конфігурації PostgreSQL."
265
+ how: "Читання pg_settings показує runtime конфігурацію."
266
+ nuances:
267
+ - "Деякі параметри вимагають перезапуску (context = 'postmaster')."
268
+ - "Перевіряйте source — звідки взято значення (default, configuration file, override)."
269
+ - "Використовуйте ALTER SYSTEM для постійних змін."
270
+
271
+ extensions:
272
+ title: "Extensions"
273
+ what: "Встановлені розширення PostgreSQL."
274
+ how: "Список з pg_extension."
275
+ nuances:
276
+ - "pg_stat_statements — must have для моніторингу продуктивності."
277
+ - "pg_trgm — для LIKE '%pattern%' запитів."
278
+ - "btree_gin/btree_gist — для комбінованих GIN/GiST індексів."
279
+ - "Перевіряйте сумісність версій при оновленні PostgreSQL."
280
+
281
+ activity_overview:
282
+ title: "Activity Overview"
283
+ what: "Загальна зведення поточної активності бази даних."
284
+ how: "Агрегація pg_stat_activity: активні/idle/waiting запити, транзакції."
285
+ nuances:
286
+ - "Корисно для швидкої оцінки стану бази."
287
+ - "Порівнюйте з baseline для виявлення аномалій."
288
+
289
+ cache_stats:
290
+ title: "Cache Stats"
291
+ what: "Статистика кешування бази даних."
292
+ how: "Дані з pg_stat_database показують blks_read vs blks_hit."
293
+ nuances:
294
+ - "Cache hit ratio = blks_hit / (blks_hit + blks_read)."
295
+ - "Цільове значення: >99% для OLTP, >95% для mixed workload."
296
+ - "Низький cache hit: збільште shared_buffers (до 25% RAM), або проблема в запитах."
297
+
298
+ # Problem explanations for highlighting
299
+ problems:
300
+ high_mean_time: "Високий середній час виконання. Розгляньте додавання індексів або оптимізацію запиту."
301
+ high_calls: "Дуже часті виклики. Розгляньте кешування або batch-операції."
302
+ high_total_time: "Запит споживає багато загального часу. Пріоритет для оптимізації."
303
+ low_cache_hit: "Низький cache hit ratio. Запит часто читає з диска замість кешу."
304
+ high_seq_scan: "Багато sequential scan. Можливо не вистачає індексу."
305
+ unused_index: "Індекс не використовується. Кандидат на видалення."
306
+ high_bloat: "Високий bloat. Потрібен REINDEX або VACUUM."
307
+ many_dead_tuples: "Багато мертвих рядків. Потрібен VACUUM."
308
+ long_running: "Довго виконуваний запит. Може блокувати інші операції."
309
+ blocking: "Блокує інші запити. Потребує уваги."
310
+ idle_in_transaction: "Відкрита транзакція без активності. Блокує VACUUM та утримує локи."
data/config/routes.rb CHANGED
@@ -5,6 +5,8 @@ PgReports::Engine.routes.draw do
5
5
 
6
6
  post "enable_pg_stat_statements", to: "dashboard#enable_pg_stat_statements", as: :enable_pg_stat_statements
7
7
  post "reset_statistics", to: "dashboard#reset_statistics", as: :reset_statistics
8
+ post "explain_analyze", to: "dashboard#explain_analyze", as: :explain_analyze
9
+ post "create_migration", to: "dashboard#create_migration", as: :create_migration
8
10
 
9
11
  get ":category/:report", to: "dashboard#show", as: :report
10
12
  post ":category/:report/run", to: "dashboard#run", as: :run_report
@@ -4,6 +4,144 @@ module PgReports
4
4
  module Dashboard
5
5
  # Registry of all available reports for the dashboard
6
6
  module ReportsRegistry
7
+ # Thresholds and problem field configuration for each report
8
+ # Text documentation is stored in I18n locale files (config/locales/*.yml)
9
+ REPORT_CONFIG = {
10
+ # === QUERIES ===
11
+ slow_queries: {
12
+ thresholds: {mean_time_ms: {warning: 100, critical: 500}},
13
+ problem_fields: ["mean_time_ms"]
14
+ },
15
+ heavy_queries: {
16
+ thresholds: {calls: {warning: 10000, critical: 100000}},
17
+ problem_fields: ["calls"]
18
+ },
19
+ expensive_queries: {
20
+ thresholds: {total_time_ms: {warning: 60000, critical: 300000}},
21
+ problem_fields: ["total_time_ms"]
22
+ },
23
+ missing_index_queries: {
24
+ thresholds: {seq_tup_read: {warning: 100000, critical: 1000000}},
25
+ problem_fields: ["seq_tup_read", "seq_scan"]
26
+ },
27
+ low_cache_hit_queries: {
28
+ thresholds: {cache_hit_ratio: {warning: 0.95, critical: 0.80, inverted: true}},
29
+ problem_fields: ["cache_hit_ratio"]
30
+ },
31
+ all_queries: {
32
+ thresholds: {},
33
+ problem_fields: []
34
+ },
35
+
36
+ # === INDEXES ===
37
+ unused_indexes: {
38
+ thresholds: {idx_scan: {warning: 10, critical: 0, inverted: true}},
39
+ problem_fields: ["idx_scan"]
40
+ },
41
+ duplicate_indexes: {
42
+ thresholds: {},
43
+ problem_fields: []
44
+ },
45
+ invalid_indexes: {
46
+ thresholds: {},
47
+ problem_fields: []
48
+ },
49
+ missing_indexes: {
50
+ thresholds: {seq_scan_ratio: {warning: 0.5, critical: 0.9}},
51
+ problem_fields: ["seq_scan", "seq_tup_read"]
52
+ },
53
+ index_usage: {
54
+ thresholds: {},
55
+ problem_fields: []
56
+ },
57
+ bloated_indexes: {
58
+ thresholds: {bloat_ratio: {warning: 0.3, critical: 0.5}},
59
+ problem_fields: ["bloat_ratio", "bloat_size"]
60
+ },
61
+ index_sizes: {
62
+ thresholds: {size_bytes: {warning: 1073741824, critical: 10737418240}},
63
+ problem_fields: ["size_bytes"]
64
+ },
65
+
66
+ # === TABLES ===
67
+ table_sizes: {
68
+ thresholds: {total_size_bytes: {warning: 10737418240, critical: 107374182400}},
69
+ problem_fields: ["total_size_bytes"]
70
+ },
71
+ bloated_tables: {
72
+ thresholds: {dead_tuple_ratio: {warning: 0.1, critical: 0.2}},
73
+ problem_fields: ["dead_tuple_ratio", "n_dead_tup"]
74
+ },
75
+ vacuum_needed: {
76
+ thresholds: {n_dead_tup: {warning: 10000, critical: 100000}},
77
+ problem_fields: ["n_dead_tup"]
78
+ },
79
+ row_counts: {
80
+ thresholds: {},
81
+ problem_fields: []
82
+ },
83
+ cache_hit_ratios: {
84
+ thresholds: {cache_hit_ratio: {warning: 0.95, critical: 0.80, inverted: true}},
85
+ problem_fields: ["cache_hit_ratio"]
86
+ },
87
+ seq_scans: {
88
+ thresholds: {seq_scan: {warning: 1000, critical: 10000}},
89
+ problem_fields: ["seq_scan", "seq_tup_read"]
90
+ },
91
+ recently_modified: {
92
+ thresholds: {},
93
+ problem_fields: []
94
+ },
95
+
96
+ # === CONNECTIONS ===
97
+ active_connections: {
98
+ thresholds: {connection_count: {warning: 50, critical: 100}},
99
+ problem_fields: ["connection_count"]
100
+ },
101
+ connection_stats: {
102
+ thresholds: {idle_in_transaction: {warning: 5, critical: 20}},
103
+ problem_fields: ["idle_in_transaction"]
104
+ },
105
+ long_running_queries: {
106
+ thresholds: {duration_seconds: {warning: 60, critical: 300}},
107
+ problem_fields: ["duration_seconds", "duration"]
108
+ },
109
+ blocking_queries: {
110
+ thresholds: {blocked_count: {warning: 1, critical: 5}},
111
+ problem_fields: ["blocked_count"]
112
+ },
113
+ locks: {
114
+ thresholds: {waiting_locks: {warning: 5, critical: 20}},
115
+ problem_fields: ["waiting_locks"]
116
+ },
117
+ idle_connections: {
118
+ thresholds: {idle_count: {warning: 30, critical: 80}},
119
+ problem_fields: ["idle_count"]
120
+ },
121
+
122
+ # === SYSTEM ===
123
+ database_sizes: {
124
+ thresholds: {size_bytes: {warning: 10737418240, critical: 107374182400}},
125
+ problem_fields: ["size_bytes"]
126
+ },
127
+ settings: {
128
+ thresholds: {},
129
+ problem_fields: []
130
+ },
131
+ extensions: {
132
+ thresholds: {},
133
+ problem_fields: []
134
+ },
135
+ activity_overview: {
136
+ thresholds: {},
137
+ problem_fields: []
138
+ },
139
+ cache_stats: {
140
+ thresholds: {cache_hit_ratio: {warning: 0.95, critical: 0.90, inverted: true}},
141
+ problem_fields: ["cache_hit_ratio"]
142
+ }
143
+ }.freeze
144
+
7
145
  REPORTS = {
8
146
  queries: {
9
147
  name: "Queries",
@@ -84,6 +222,38 @@ module PgReports
84
222
  def self.category(category)
85
223
  REPORTS[category.to_sym]
86
224
  end
225
+
226
+ # Returns full documentation for a report including I18n translations
227
+ def self.documentation(report)
228
+ report_key = report.to_sym
229
+ config = REPORT_CONFIG[report_key] || {thresholds: {}, problem_fields: []}
230
+
231
+ # Get translations from I18n
232
+ i18n_key = "pg_reports.documentation.#{report_key}"
233
+ {
234
+ title: I18n.t("#{i18n_key}.title", default: report.to_s.titleize),
235
+ what: I18n.t("#{i18n_key}.what", default: ""),
236
+ how: I18n.t("#{i18n_key}.how", default: ""),
237
+ nuances: I18n.t("#{i18n_key}.nuances", default: []),
238
+ thresholds: config[:thresholds],
239
+ problem_fields: config[:problem_fields]
240
+ }
241
+ end
242
+
243
+ # Returns the problem explanation for a given problem type
244
+ def self.problem_explanation(problem_key)
245
+ I18n.t("pg_reports.problems.#{problem_key}", default: "")
246
+ end
247
+
248
+ # Returns thresholds for a report
249
+ def self.thresholds(report)
250
+ REPORT_CONFIG.dig(report.to_sym, :thresholds) || {}
251
+ end
252
+
253
+ # Returns problem fields for a report
254
+ def self.problem_fields(report)
255
+ REPORT_CONFIG.dig(report.to_sym, :problem_fields) || []
256
+ end
87
257
  end
88
258
  end
89
259
  end
@@ -8,6 +8,11 @@ module PgReports
8
8
  g.test_framework :rspec
9
9
  end
10
10
 
11
+ # Load locales from the engine
12
+ initializer "pg_reports.load_locales" do |app|
13
+ config.i18n.load_path += Dir[root.join("config", "locales", "*.yml")]
14
+ end
15
+
11
16
  initializer "pg_reports.assets" do |_app|
12
17
  # Assets are inline in views, no precompilation needed
13
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgReports
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
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.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eldar Avatov
@@ -109,6 +109,9 @@ files:
109
109
  - app/views/layouts/pg_reports/application.html.erb
110
110
  - app/views/pg_reports/dashboard/index.html.erb
111
111
  - app/views/pg_reports/dashboard/show.html.erb
112
+ - config/locales/en.yml
113
+ - config/locales/ru.yml
114
+ - config/locales/uk.yml
112
115
  - config/routes.rb
113
116
  - lib/pg_reports.rb
114
117
  - lib/pg_reports/annotation_parser.rb