pg_reports 0.9.0 → 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/README.md +12 -8
- data/app/controllers/pg_reports/dashboard_controller.rb +277 -221
- data/app/controllers/pg_reports/metrics_controller.rb +13 -4
- data/app/views/layouts/pg_reports/application.html.erb +222 -93
- data/app/views/pg_reports/dashboard/_settings_modal.html.erb +55 -0
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +1 -42
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +75 -105
- data/app/views/pg_reports/dashboard/index.html.erb +346 -238
- data/app/views/pg_reports/dashboard/show.html.erb +1 -1
- data/bin/pg_reports +2 -2
- data/config/brakeman.ignore +10 -56
- data/config/locales/en.yml +45 -11
- data/config/locales/ru.yml +44 -11
- data/config/locales/uk.yml +44 -11
- data/config/routes.rb +2 -0
- data/lib/pg_reports/access_policy.rb +39 -0
- data/lib/pg_reports/compatibility.rb +10 -2
- data/lib/pg_reports/configuration.rb +33 -2
- data/lib/pg_reports/connection/error_translator.rb +21 -0
- data/lib/pg_reports/connection/target.rb +43 -10
- data/lib/pg_reports/csv_cell.rb +20 -0
- data/lib/pg_reports/dashboard/reports_registry.rb +37 -30
- data/lib/pg_reports/definitions/connections/connection_stats.yml +4 -0
- data/lib/pg_reports/definitions/connections/idle_connections.yml +4 -0
- data/lib/pg_reports/definitions/connections/locks.yml +1 -0
- data/lib/pg_reports/definitions/connections/long_running_queries.yml +0 -1
- data/lib/pg_reports/definitions/indexes/bloated_indexes.yml +1 -2
- data/lib/pg_reports/definitions/indexes/index_correlation.yml +2 -1
- data/lib/pg_reports/definitions/indexes/missing_indexes.yml +1 -0
- data/lib/pg_reports/definitions/queries/frequent_queries.yml +50 -0
- data/lib/pg_reports/definitions/system/cache_stats.yml +2 -1
- data/lib/pg_reports/definitions/tables/bloated_tables.yml +1 -2
- data/lib/pg_reports/definitions/tables/recently_modified.yml +3 -3
- data/lib/pg_reports/definitions/tables/unused_tables.yml +1 -1
- data/lib/pg_reports/drop_index_migration.rb +56 -0
- data/lib/pg_reports/engine.rb +4 -3
- data/lib/pg_reports/executor.rb +46 -9
- data/lib/pg_reports/modules/queries.rb +1 -0
- data/lib/pg_reports/query_monitor.rb +478 -192
- data/lib/pg_reports/rate_limiter.rb +63 -0
- data/lib/pg_reports/report.rb +1 -1
- data/lib/pg_reports/report_definition.rb +11 -1
- data/lib/pg_reports/sql/connections/connection_stats.sql +2 -1
- data/lib/pg_reports/sql/connections/idle_connections.sql +1 -0
- data/lib/pg_reports/sql/connections/locks.sql +1 -0
- data/lib/pg_reports/sql/indexes/bloated_indexes.sql +16 -8
- data/lib/pg_reports/sql/queries/activity_sample.sql +22 -0
- data/lib/pg_reports/sql/queries/frequent_queries.sql +42 -0
- data/lib/pg_reports/sql_redactor.rb +32 -0
- data/lib/pg_reports/standalone.rb +83 -20
- data/lib/pg_reports/version.rb +1 -1
- data/lib/pg_reports.rb +7 -2
- metadata +18 -9
data/lib/pg_reports/executor.rb
CHANGED
|
@@ -7,25 +7,39 @@ module PgReports
|
|
|
7
7
|
# context set by PgReports.with_target / with_database is honored even when an
|
|
8
8
|
# Executor instance has been memoized at the module level.
|
|
9
9
|
class Executor
|
|
10
|
+
# Marks the SQL as ours where a statement name cannot reach: inside a host
|
|
11
|
+
# application these queries share its connections and application_name, so
|
|
12
|
+
# this is all that tells them apart in pg_stat_activity.
|
|
13
|
+
QUERY_TAG = "/* pg_reports */"
|
|
14
|
+
|
|
15
|
+
# A report that needs a lock someone else holds has nothing to wait for: during
|
|
16
|
+
# an incident the answer is more use now than after the full statement timeout.
|
|
17
|
+
REPORT_LOCK_TIMEOUT_MS = 3_000
|
|
18
|
+
|
|
19
|
+
# Not the second colon of a :: cast, nor the middle of a word
|
|
20
|
+
PLACEHOLDER = /(?<![:\w]):([a-zA-Z_]\w*)/
|
|
21
|
+
|
|
10
22
|
def initialize(connection: nil)
|
|
11
23
|
@connection_override = connection
|
|
12
24
|
end
|
|
13
25
|
|
|
14
|
-
# Execute SQL from a file and return results as array of hashes
|
|
26
|
+
# Execute SQL from a file and return results as array of hashes.
|
|
27
|
+
#
|
|
28
|
+
# These files only read, so they run under the report time limits.
|
|
15
29
|
def execute_from_file(category, name, **params)
|
|
16
30
|
sql = SqlLoader.load(category, name)
|
|
17
|
-
execute(sql, **params)
|
|
31
|
+
within_report_limits { execute(sql, **params) }
|
|
18
32
|
end
|
|
19
33
|
|
|
20
34
|
# Execute raw SQL and return results as array of hashes.
|
|
21
35
|
#
|
|
22
36
|
# Every query is tagged with the "PgReports" AR statement name so the Query
|
|
23
|
-
# Monitor can skip our own queries by name (see QueryMonitor#
|
|
37
|
+
# Monitor can skip our own queries by name (see QueryMonitor#skip_by_payload?),
|
|
24
38
|
# reliably and independent of backtrace depth — the internal live_metrics /
|
|
25
39
|
# status polling would otherwise leak into the monitor's history.
|
|
26
40
|
def execute(sql, **params)
|
|
27
41
|
processed_sql = interpolate_params(sql, params)
|
|
28
|
-
result = connection.exec_query(processed_sql, "PgReports")
|
|
42
|
+
result = connection.exec_query("#{QUERY_TAG} #{processed_sql}", "PgReports")
|
|
29
43
|
result.to_a
|
|
30
44
|
end
|
|
31
45
|
|
|
@@ -36,14 +50,37 @@ module PgReports
|
|
|
36
50
|
|
|
37
51
|
private
|
|
38
52
|
|
|
39
|
-
#
|
|
40
|
-
#
|
|
53
|
+
# The limits are local to a transaction, and a savepoint inside the host
|
|
54
|
+
# application's transaction would hand them on to it when released. Rolling back
|
|
55
|
+
# discards them in both cases, and there is nothing here to keep.
|
|
56
|
+
def within_report_limits
|
|
57
|
+
timeout_ms = PgReports.config.report_statement_timeout_ms.to_i
|
|
58
|
+
return yield unless timeout_ms.positive?
|
|
59
|
+
|
|
60
|
+
rows = nil
|
|
61
|
+
connection.transaction(requires_new: true) do
|
|
62
|
+
connection.exec_query(report_limits_sql(timeout_ms), "PgReports")
|
|
63
|
+
rows = yield
|
|
64
|
+
raise ActiveRecord::Rollback
|
|
65
|
+
end
|
|
66
|
+
rows
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def report_limits_sql(timeout_ms)
|
|
70
|
+
lock_timeout_ms = [timeout_ms, REPORT_LOCK_TIMEOUT_MS].min
|
|
71
|
+
|
|
72
|
+
"#{QUERY_TAG} SELECT set_config('statement_timeout', '#{timeout_ms}', true), " \
|
|
73
|
+
"set_config('lock_timeout', '#{lock_timeout_ms}', true)"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Replaces :param_name with quoted values in a single pass, so a value is never
|
|
77
|
+
# read back as SQL: one holding ":other" stays text, and :limit leaves :limit_x alone.
|
|
41
78
|
def interpolate_params(sql, params)
|
|
42
79
|
return sql if params.empty?
|
|
43
80
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
81
|
+
sql.gsub(PLACEHOLDER) do |placeholder|
|
|
82
|
+
key = Regexp.last_match(1).to_sym
|
|
83
|
+
params.key?(key) ? quote_value(params[key]) : placeholder
|
|
47
84
|
end
|
|
48
85
|
end
|
|
49
86
|
|
|
@@ -10,6 +10,7 @@ module PgReports
|
|
|
10
10
|
# The following methods are auto-generated from YAML:
|
|
11
11
|
# - slow_queries(limit: 20)
|
|
12
12
|
# - heavy_queries(limit: 20)
|
|
13
|
+
# - frequent_queries(limit: 20)
|
|
13
14
|
# - expensive_queries(limit: 20)
|
|
14
15
|
# - missing_index_queries(limit: 20)
|
|
15
16
|
# - low_cache_hit_queries(limit: 20, min_calls: 100)
|