pghero_fork 2.7.3
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 +7 -0
- data/CHANGELOG.md +391 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +3 -0
- data/app/assets/images/pghero/favicon.png +0 -0
- data/app/assets/javascripts/pghero/Chart.bundle.js +20755 -0
- data/app/assets/javascripts/pghero/application.js +158 -0
- data/app/assets/javascripts/pghero/chartkick.js +2436 -0
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
- data/app/assets/javascripts/pghero/jquery.js +10872 -0
- data/app/assets/javascripts/pghero/nouislider.js +2672 -0
- data/app/assets/stylesheets/pghero/application.css +514 -0
- data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
- data/app/assets/stylesheets/pghero/nouislider.css +310 -0
- data/app/controllers/pg_hero/home_controller.rb +449 -0
- data/app/helpers/pg_hero/home_helper.rb +30 -0
- data/app/views/layouts/pg_hero/application.html.erb +68 -0
- data/app/views/pg_hero/home/_connections_table.html.erb +16 -0
- data/app/views/pg_hero/home/_live_queries_table.html.erb +51 -0
- data/app/views/pg_hero/home/_queries_table.html.erb +72 -0
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +16 -0
- data/app/views/pg_hero/home/_suggested_index.html.erb +18 -0
- data/app/views/pg_hero/home/connections.html.erb +32 -0
- data/app/views/pg_hero/home/explain.html.erb +27 -0
- data/app/views/pg_hero/home/index.html.erb +518 -0
- data/app/views/pg_hero/home/index_bloat.html.erb +72 -0
- data/app/views/pg_hero/home/live_queries.html.erb +11 -0
- data/app/views/pg_hero/home/maintenance.html.erb +55 -0
- data/app/views/pg_hero/home/queries.html.erb +33 -0
- data/app/views/pg_hero/home/relation_space.html.erb +14 -0
- data/app/views/pg_hero/home/show_query.html.erb +106 -0
- data/app/views/pg_hero/home/space.html.erb +83 -0
- data/app/views/pg_hero/home/system.html.erb +34 -0
- data/app/views/pg_hero/home/tune.html.erb +53 -0
- data/config/routes.rb +32 -0
- data/lib/generators/pghero/config_generator.rb +13 -0
- data/lib/generators/pghero/query_stats_generator.rb +18 -0
- data/lib/generators/pghero/space_stats_generator.rb +18 -0
- data/lib/generators/pghero/templates/config.yml.tt +46 -0
- data/lib/generators/pghero/templates/query_stats.rb.tt +15 -0
- data/lib/generators/pghero/templates/space_stats.rb.tt +13 -0
- data/lib/pghero.rb +246 -0
- data/lib/pghero/connection.rb +5 -0
- data/lib/pghero/database.rb +175 -0
- data/lib/pghero/engine.rb +16 -0
- data/lib/pghero/methods/basic.rb +160 -0
- data/lib/pghero/methods/connections.rb +77 -0
- data/lib/pghero/methods/constraints.rb +30 -0
- data/lib/pghero/methods/explain.rb +29 -0
- data/lib/pghero/methods/indexes.rb +332 -0
- data/lib/pghero/methods/kill.rb +28 -0
- data/lib/pghero/methods/maintenance.rb +93 -0
- data/lib/pghero/methods/queries.rb +75 -0
- data/lib/pghero/methods/query_stats.rb +349 -0
- data/lib/pghero/methods/replication.rb +74 -0
- data/lib/pghero/methods/sequences.rb +124 -0
- data/lib/pghero/methods/settings.rb +37 -0
- data/lib/pghero/methods/space.rb +141 -0
- data/lib/pghero/methods/suggested_indexes.rb +329 -0
- data/lib/pghero/methods/system.rb +287 -0
- data/lib/pghero/methods/tables.rb +68 -0
- data/lib/pghero/methods/users.rb +87 -0
- data/lib/pghero/query_stats.rb +5 -0
- data/lib/pghero/space_stats.rb +5 -0
- data/lib/pghero/stats.rb +6 -0
- data/lib/pghero/version.rb +3 -0
- data/lib/tasks/pghero.rake +27 -0
- data/licenses/LICENSE-chart.js.txt +9 -0
- data/licenses/LICENSE-chartkick.js.txt +22 -0
- data/licenses/LICENSE-highlight.js.txt +29 -0
- data/licenses/LICENSE-jquery.txt +20 -0
- data/licenses/LICENSE-moment.txt +22 -0
- data/licenses/LICENSE-nouislider.txt +21 -0
- metadata +130 -0
data/config/routes.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
PgHero::Engine.routes.draw do
|
2
|
+
scope "(:database)", constraints: proc { |req| (PgHero.config["databases"].keys + [nil]).include?(req.params[:database]) } do
|
3
|
+
get "space", to: "home#space"
|
4
|
+
get "space/:relation", to: "home#relation_space", as: :relation_space
|
5
|
+
get "index_bloat", to: "home#index_bloat"
|
6
|
+
get "live_queries", to: "home#live_queries"
|
7
|
+
get "queries", to: "home#queries"
|
8
|
+
get "queries/:query_hash", to: "home#show_query", as: :show_query
|
9
|
+
get "system", to: "home#system"
|
10
|
+
get "cpu_usage", to: "home#cpu_usage"
|
11
|
+
get "connection_stats", to: "home#connection_stats"
|
12
|
+
get "replication_lag_stats", to: "home#replication_lag_stats"
|
13
|
+
get "load_stats", to: "home#load_stats"
|
14
|
+
get "free_space_stats", to: "home#free_space_stats"
|
15
|
+
get "explain", to: "home#explain"
|
16
|
+
get "tune", to: "home#tune"
|
17
|
+
get "connections", to: "home#connections"
|
18
|
+
get "maintenance", to: "home#maintenance"
|
19
|
+
post "kill", to: "home#kill"
|
20
|
+
post "kill_long_running_queries", to: "home#kill_long_running_queries"
|
21
|
+
post "kill_all", to: "home#kill_all"
|
22
|
+
post "enable_query_stats", to: "home#enable_query_stats"
|
23
|
+
post "explain", to: "home#explain"
|
24
|
+
post "reset_query_stats", to: "home#reset_query_stats"
|
25
|
+
|
26
|
+
# legacy routes
|
27
|
+
get "system_stats" => redirect("system")
|
28
|
+
get "query_stats" => redirect("queries")
|
29
|
+
|
30
|
+
root to: "home#index"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Pghero
|
4
|
+
module Generators
|
5
|
+
class ConfigGenerator < Rails::Generators::Base
|
6
|
+
source_root File.join(__dir__, "templates")
|
7
|
+
|
8
|
+
def create_initializer
|
9
|
+
template "config.yml", "config/pghero.yml"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "rails/generators/active_record"
|
2
|
+
|
3
|
+
module Pghero
|
4
|
+
module Generators
|
5
|
+
class QueryStatsGenerator < Rails::Generators::Base
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
8
|
+
|
9
|
+
def copy_migration
|
10
|
+
migration_template "query_stats.rb", "db/migrate/create_pghero_query_stats.rb", migration_version: migration_version
|
11
|
+
end
|
12
|
+
|
13
|
+
def migration_version
|
14
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "rails/generators/active_record"
|
2
|
+
|
3
|
+
module Pghero
|
4
|
+
module Generators
|
5
|
+
class SpaceStatsGenerator < Rails::Generators::Base
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
8
|
+
|
9
|
+
def copy_migration
|
10
|
+
migration_template "space_stats.rb", "db/migrate/create_pghero_space_stats.rb", migration_version: migration_version
|
11
|
+
end
|
12
|
+
|
13
|
+
def migration_version
|
14
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
databases:
|
2
|
+
primary:
|
3
|
+
# Database URL (defaults to app database)
|
4
|
+
# url: <%%= ENV["DATABASE_URL"] %>
|
5
|
+
|
6
|
+
# System stats
|
7
|
+
# aws_db_instance_identifier: my-instance
|
8
|
+
# gcp_database_id: my-project:my-instance
|
9
|
+
# azure_resource_id: my-resource-id
|
10
|
+
|
11
|
+
# Add more databases
|
12
|
+
# other:
|
13
|
+
# url: <%%= ENV["OTHER_DATABASE_URL"] %>
|
14
|
+
|
15
|
+
# Minimum time for long running queries
|
16
|
+
# long_running_query_sec: 60
|
17
|
+
|
18
|
+
# Minimum average time for slow queries
|
19
|
+
# slow_query_ms: 20
|
20
|
+
|
21
|
+
# Minimum calls for slow queries
|
22
|
+
# slow_query_calls: 100
|
23
|
+
|
24
|
+
# Minimum connections for high connections warning
|
25
|
+
# total_connections_threshold: 500
|
26
|
+
|
27
|
+
# Statement timeout for explain
|
28
|
+
# explain_timeout_sec: 10
|
29
|
+
|
30
|
+
# Time zone (defaults to app time zone)
|
31
|
+
# time_zone: "Pacific Time (US & Canada)"
|
32
|
+
|
33
|
+
# Basic authentication
|
34
|
+
# username: admin
|
35
|
+
# password: <%%= ENV["PGHERO_PASSWORD"] %>
|
36
|
+
|
37
|
+
# Stats database URL (defaults to app database)
|
38
|
+
# stats_database_url: <%%= ENV["PGHERO_STATS_DATABASE_URL"] %>
|
39
|
+
|
40
|
+
# AWS configuration (defaults to app AWS config)
|
41
|
+
# aws_access_key_id: <%%= ENV["AWS_ACCESS_KEY_ID"] %>
|
42
|
+
# aws_secret_access_key: <%%= ENV["AWS_SECRET_ACCESS_KEY"] %>
|
43
|
+
# aws_region: us-east-1
|
44
|
+
|
45
|
+
# Filter data from queries (experimental)
|
46
|
+
# filter_data: true
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :pghero_query_stats do |t|
|
4
|
+
t.text :database
|
5
|
+
t.text :user
|
6
|
+
t.text :query
|
7
|
+
t.integer :query_hash, limit: 8
|
8
|
+
t.float :total_time
|
9
|
+
t.integer :calls, limit: 8
|
10
|
+
t.timestamp :captured_at
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index :pghero_query_stats, [:database, :captured_at]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :pghero_space_stats do |t|
|
4
|
+
t.text :database
|
5
|
+
t.text :schema
|
6
|
+
t.text :relation
|
7
|
+
t.integer :size, limit: 8
|
8
|
+
t.timestamp :captured_at
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :pghero_space_stats, [:database, :captured_at]
|
12
|
+
end
|
13
|
+
end
|
data/lib/pghero.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
# dependencies
|
2
|
+
require "active_support"
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
# methods
|
6
|
+
require "pghero/methods/basic"
|
7
|
+
require "pghero/methods/connections"
|
8
|
+
require "pghero/methods/constraints"
|
9
|
+
require "pghero/methods/explain"
|
10
|
+
require "pghero/methods/indexes"
|
11
|
+
require "pghero/methods/kill"
|
12
|
+
require "pghero/methods/maintenance"
|
13
|
+
require "pghero/methods/queries"
|
14
|
+
require "pghero/methods/query_stats"
|
15
|
+
require "pghero/methods/replication"
|
16
|
+
require "pghero/methods/sequences"
|
17
|
+
require "pghero/methods/settings"
|
18
|
+
require "pghero/methods/space"
|
19
|
+
require "pghero/methods/suggested_indexes"
|
20
|
+
require "pghero/methods/system"
|
21
|
+
require "pghero/methods/tables"
|
22
|
+
require "pghero/methods/users"
|
23
|
+
|
24
|
+
require "pghero/database"
|
25
|
+
require "pghero/engine" if defined?(Rails)
|
26
|
+
require "pghero/version"
|
27
|
+
|
28
|
+
module PgHero
|
29
|
+
autoload :Connection, "pghero/connection"
|
30
|
+
autoload :Stats, "pghero/stats"
|
31
|
+
autoload :QueryStats, "pghero/query_stats"
|
32
|
+
autoload :SpaceStats, "pghero/space_stats"
|
33
|
+
|
34
|
+
class Error < StandardError; end
|
35
|
+
class NotEnabled < Error; end
|
36
|
+
|
37
|
+
MUTEX = Mutex.new
|
38
|
+
|
39
|
+
# settings
|
40
|
+
class << self
|
41
|
+
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path, :filter_data
|
42
|
+
end
|
43
|
+
self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
|
44
|
+
self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
|
45
|
+
self.slow_query_calls = (ENV["PGHERO_SLOW_QUERY_CALLS"] || 100).to_i
|
46
|
+
self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).to_f
|
47
|
+
self.total_connections_threshold = (ENV["PGHERO_TOTAL_CONNECTIONS_THRESHOLD"] || 500).to_i
|
48
|
+
self.cache_hit_rate_threshold = 99
|
49
|
+
self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
50
|
+
self.show_migrations = true
|
51
|
+
self.config_path = ENV["PGHERO_CONFIG_PATH"] || "config/pghero.yml"
|
52
|
+
self.filter_data = ENV["PGHERO_FILTER_DATA"].to_s.size > 0
|
53
|
+
|
54
|
+
class << self
|
55
|
+
extend Forwardable
|
56
|
+
def_delegators :primary_database, :access_key_id, :analyze, :analyze_tables, :autoindex, :autovacuum_danger,
|
57
|
+
:best_index, :blocked_queries, :connections, :connection_sources, :connection_states, :connection_stats,
|
58
|
+
:cpu_usage, :create_user, :database_size, :db_instance_identifier, :disable_query_stats, :drop_user,
|
59
|
+
:duplicate_indexes, :enable_query_stats, :explain, :historical_query_stats_enabled?, :index_caching,
|
60
|
+
:index_hit_rate, :index_usage, :indexes, :invalid_constraints, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
|
61
|
+
:last_stats_reset_time, :long_running_queries, :maintenance_info, :missing_indexes, :query_stats,
|
62
|
+
:query_stats_available?, :query_stats_enabled?, :query_stats_extension_enabled?, :query_stats_readable?,
|
63
|
+
:rds_stats, :read_iops_stats, :region, :relation_sizes, :replica?, :replication_lag, :replication_lag_stats,
|
64
|
+
:reset_query_stats, :reset_stats, :running_queries, :secret_access_key, :sequence_danger, :sequences, :settings,
|
65
|
+
:slow_queries, :space_growth, :ssl_used?, :stats_connection, :suggested_indexes, :suggested_indexes_by_query,
|
66
|
+
:suggested_indexes_enabled?, :system_stats_enabled?, :table_caching, :table_hit_rate, :table_stats,
|
67
|
+
:total_connections, :transaction_id_danger, :unused_indexes, :unused_tables, :write_iops_stats
|
68
|
+
|
69
|
+
def time_zone=(time_zone)
|
70
|
+
@time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
|
71
|
+
end
|
72
|
+
|
73
|
+
def time_zone
|
74
|
+
@time_zone || Time.zone
|
75
|
+
end
|
76
|
+
|
77
|
+
# use method instead of attr_accessor to ensure
|
78
|
+
# this works if variable set after PgHero is loaded
|
79
|
+
def username
|
80
|
+
@username ||= config["username"] || ENV["PGHERO_USERNAME"]
|
81
|
+
end
|
82
|
+
|
83
|
+
# use method instead of attr_accessor to ensure
|
84
|
+
# this works if variable set after PgHero is loaded
|
85
|
+
def password
|
86
|
+
@password ||= config["password"] || ENV["PGHERO_PASSWORD"]
|
87
|
+
end
|
88
|
+
|
89
|
+
def stats_database_url
|
90
|
+
@stats_database_url ||= config["stats_database_url"] || ENV["PGHERO_STATS_DATABASE_URL"]
|
91
|
+
end
|
92
|
+
|
93
|
+
def config
|
94
|
+
@config ||= begin
|
95
|
+
require "erb"
|
96
|
+
require "yaml"
|
97
|
+
|
98
|
+
path = config_path
|
99
|
+
|
100
|
+
config_file_exists = File.exist?(path)
|
101
|
+
|
102
|
+
config = YAML.load(ERB.new(File.read(path)).result) if config_file_exists
|
103
|
+
config ||= {}
|
104
|
+
|
105
|
+
if config[env]
|
106
|
+
config[env]
|
107
|
+
elsif config["databases"] # preferred format
|
108
|
+
config
|
109
|
+
elsif config_file_exists
|
110
|
+
raise "Invalid config file"
|
111
|
+
else
|
112
|
+
databases = {}
|
113
|
+
|
114
|
+
if !ENV["PGHERO_DATABASE_URL"] && spec_supported?
|
115
|
+
ActiveRecord::Base.configurations.configs_for(env_name: env, include_replicas: true).each do |db|
|
116
|
+
databases[db.send(spec_name_key)] = {"spec" => db.send(spec_name_key)}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if databases.empty?
|
121
|
+
databases["primary"] = {
|
122
|
+
"url" => ENV["PGHERO_DATABASE_URL"] || connection_config(ActiveRecord::Base)
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
if databases.size == 1
|
127
|
+
databases.values.first.merge!(
|
128
|
+
"db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"],
|
129
|
+
"gcp_database_id" => ENV["PGHERO_GCP_DATABASE_ID"],
|
130
|
+
"azure_resource_id" => ENV["PGHERO_AZURE_RESOURCE_ID"]
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
{
|
135
|
+
"databases" => databases
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# ensure we only have one copy of databases
|
142
|
+
# so there's only one connection pool per database
|
143
|
+
def databases
|
144
|
+
unless defined?(@databases)
|
145
|
+
# only use mutex on initialization
|
146
|
+
MUTEX.synchronize do
|
147
|
+
# return if another process initialized while we were waiting
|
148
|
+
return @databases if defined?(@databases)
|
149
|
+
|
150
|
+
@databases = config["databases"].map { |id, c| [id.to_sym, Database.new(id, c)] }.to_h
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
@databases
|
155
|
+
end
|
156
|
+
|
157
|
+
def primary_database
|
158
|
+
databases.values.first
|
159
|
+
end
|
160
|
+
|
161
|
+
def capture_query_stats(verbose: false)
|
162
|
+
each_database do |database|
|
163
|
+
next unless database.capture_query_stats?
|
164
|
+
puts "Capturing query stats for #{database.id}..." if verbose
|
165
|
+
database.capture_query_stats(raise_errors: true)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def capture_space_stats(verbose: false)
|
170
|
+
each_database do |database|
|
171
|
+
puts "Capturing space stats for #{database.id}..." if verbose
|
172
|
+
database.capture_space_stats
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def analyze_all(**options)
|
177
|
+
each_database do |database|
|
178
|
+
next if database.replica?
|
179
|
+
database.analyze_tables(**options)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def autoindex_all(create: false, verbose: true)
|
184
|
+
each_database do |database|
|
185
|
+
puts "Autoindexing #{database.id}..." if verbose
|
186
|
+
database.autoindex(create: create)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def pretty_size(value)
|
191
|
+
ActiveSupport::NumberHelper.number_to_human_size(value, precision: 3)
|
192
|
+
end
|
193
|
+
|
194
|
+
# delete previous stats
|
195
|
+
# go database by database to use an index
|
196
|
+
# stats for old databases are not cleaned up since we can't use an index
|
197
|
+
def clean_query_stats
|
198
|
+
each_database do |database|
|
199
|
+
database.clean_query_stats
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def clean_space_stats
|
204
|
+
each_database do |database|
|
205
|
+
database.clean_space_stats
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# private
|
210
|
+
def spec_supported?
|
211
|
+
ActiveRecord::VERSION::MAJOR >= 6
|
212
|
+
end
|
213
|
+
|
214
|
+
# private
|
215
|
+
def connection_config(model)
|
216
|
+
ActiveRecord::VERSION::STRING.to_f >= 6.1 ? model.connection_db_config.configuration_hash : model.connection_config
|
217
|
+
end
|
218
|
+
|
219
|
+
# private
|
220
|
+
# Rails 6.1 deprecate `spec_name` and use `name` for configurations
|
221
|
+
# https://github.com/rails/rails/pull/38536
|
222
|
+
def spec_name_key
|
223
|
+
ActiveRecord::VERSION::STRING.to_f >= 6.1 ? :name : :spec_name
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
|
228
|
+
def each_database
|
229
|
+
first_error = nil
|
230
|
+
|
231
|
+
databases.each do |_, database|
|
232
|
+
begin
|
233
|
+
yield database
|
234
|
+
rescue => e
|
235
|
+
puts "#{e.class.name}: #{e.message}"
|
236
|
+
puts
|
237
|
+
first_error ||= e
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
raise first_error if first_error
|
242
|
+
|
243
|
+
true
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module PgHero
|
2
|
+
class Database
|
3
|
+
include Methods::Basic
|
4
|
+
include Methods::Connections
|
5
|
+
include Methods::Constraints
|
6
|
+
include Methods::Explain
|
7
|
+
include Methods::Indexes
|
8
|
+
include Methods::Kill
|
9
|
+
include Methods::Maintenance
|
10
|
+
include Methods::Queries
|
11
|
+
include Methods::QueryStats
|
12
|
+
include Methods::Replication
|
13
|
+
include Methods::Sequences
|
14
|
+
include Methods::Settings
|
15
|
+
include Methods::Space
|
16
|
+
include Methods::SuggestedIndexes
|
17
|
+
include Methods::System
|
18
|
+
include Methods::Tables
|
19
|
+
include Methods::Users
|
20
|
+
|
21
|
+
attr_reader :id, :config
|
22
|
+
|
23
|
+
def initialize(id, config)
|
24
|
+
@id = id
|
25
|
+
@config = config || {}
|
26
|
+
|
27
|
+
# preload model to ensure only one connection pool
|
28
|
+
# this doesn't actually start any connections
|
29
|
+
@adapter_checked = false
|
30
|
+
@connection_model = build_connection_model
|
31
|
+
end
|
32
|
+
|
33
|
+
def name
|
34
|
+
@name ||= @config["name"] || id.titleize
|
35
|
+
end
|
36
|
+
|
37
|
+
def capture_query_stats?
|
38
|
+
config["capture_query_stats"] != false
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache_hit_rate_threshold
|
42
|
+
(config["cache_hit_rate_threshold"] || PgHero.config["cache_hit_rate_threshold"] || PgHero.cache_hit_rate_threshold).to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
def total_connections_threshold
|
46
|
+
(config["total_connections_threshold"] || PgHero.config["total_connections_threshold"] || PgHero.total_connections_threshold).to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
def slow_query_ms
|
50
|
+
(config["slow_query_ms"] || PgHero.config["slow_query_ms"] || PgHero.slow_query_ms).to_i
|
51
|
+
end
|
52
|
+
|
53
|
+
def slow_query_calls
|
54
|
+
(config["slow_query_calls"] || PgHero.config["slow_query_calls"] || PgHero.slow_query_calls).to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
def explain_timeout_sec
|
58
|
+
(config["explain_timeout_sec"] || PgHero.config["explain_timeout_sec"] || PgHero.explain_timeout_sec).to_f
|
59
|
+
end
|
60
|
+
|
61
|
+
def long_running_query_sec
|
62
|
+
(config["long_running_query_sec"] || PgHero.config["long_running_query_sec"] || PgHero.long_running_query_sec).to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
# defaults to 100 megabytes
|
66
|
+
def index_bloat_bytes
|
67
|
+
(config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] || 104857600).to_i
|
68
|
+
end
|
69
|
+
|
70
|
+
def aws_access_key_id
|
71
|
+
config["aws_access_key_id"] || PgHero.config["aws_access_key_id"] || ENV["PGHERO_ACCESS_KEY_ID"] || ENV["AWS_ACCESS_KEY_ID"]
|
72
|
+
end
|
73
|
+
|
74
|
+
def aws_secret_access_key
|
75
|
+
config["aws_secret_access_key"] || PgHero.config["aws_secret_access_key"] || ENV["PGHERO_SECRET_ACCESS_KEY"] || ENV["AWS_SECRET_ACCESS_KEY"]
|
76
|
+
end
|
77
|
+
|
78
|
+
def aws_region
|
79
|
+
config["aws_region"] || PgHero.config["aws_region"] || ENV["PGHERO_REGION"] || ENV["AWS_REGION"] || (defined?(Aws) && Aws.config[:region]) || "us-east-1"
|
80
|
+
end
|
81
|
+
|
82
|
+
# environment variable is only used if no config file
|
83
|
+
def aws_db_instance_identifier
|
84
|
+
@aws_db_instance_identifier ||= config["aws_db_instance_identifier"] || config["db_instance_identifier"]
|
85
|
+
end
|
86
|
+
|
87
|
+
# environment variable is only used if no config file
|
88
|
+
def gcp_database_id
|
89
|
+
@gcp_database_id ||= config["gcp_database_id"]
|
90
|
+
end
|
91
|
+
|
92
|
+
# environment variable is only used if no config file
|
93
|
+
def azure_resource_id
|
94
|
+
@azure_resource_id ||= config["azure_resource_id"]
|
95
|
+
end
|
96
|
+
|
97
|
+
# must check keys for booleans
|
98
|
+
def filter_data
|
99
|
+
unless defined?(@filter_data)
|
100
|
+
@filter_data =
|
101
|
+
if config.key?("filter_data")
|
102
|
+
config["filter_data"]
|
103
|
+
elsif PgHero.config.key?("filter_data")
|
104
|
+
PgHero.config.key?("filter_data")
|
105
|
+
else
|
106
|
+
PgHero.filter_data
|
107
|
+
end
|
108
|
+
|
109
|
+
if @filter_data
|
110
|
+
begin
|
111
|
+
require "pg_query"
|
112
|
+
rescue LoadError
|
113
|
+
raise Error, "pg_query required for filter_data"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
@filter_data
|
119
|
+
end
|
120
|
+
|
121
|
+
# TODO remove in next major version
|
122
|
+
alias_method :access_key_id, :aws_access_key_id
|
123
|
+
alias_method :secret_access_key, :aws_secret_access_key
|
124
|
+
alias_method :region, :aws_region
|
125
|
+
alias_method :db_instance_identifier, :aws_db_instance_identifier
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# check adapter lazily
|
130
|
+
def connection_model
|
131
|
+
unless @adapter_checked
|
132
|
+
# rough check for Postgres adapter
|
133
|
+
# keep this message generic so it's useful
|
134
|
+
# when empty url set in Docker image pghero.yml
|
135
|
+
unless @connection_model.connection.adapter_name =~ /postg/i
|
136
|
+
raise Error, "Invalid connection URL"
|
137
|
+
end
|
138
|
+
@adapter_checked = true
|
139
|
+
end
|
140
|
+
|
141
|
+
@connection_model
|
142
|
+
end
|
143
|
+
|
144
|
+
# just return the model
|
145
|
+
# do not start a connection
|
146
|
+
def build_connection_model
|
147
|
+
url = config["url"]
|
148
|
+
|
149
|
+
# resolve spec
|
150
|
+
if !url && config["spec"]
|
151
|
+
raise Error, "Spec requires Rails 6+" unless PgHero.spec_supported?
|
152
|
+
config_options = {env_name: PgHero.env, PgHero.spec_name_key => config["spec"], include_replicas: true}
|
153
|
+
resolved = ActiveRecord::Base.configurations.configs_for(**config_options)
|
154
|
+
raise Error, "Spec not found: #{config["spec"]}" unless resolved
|
155
|
+
url = ActiveRecord::VERSION::STRING.to_f >= 6.1 ? resolved.configuration_hash : resolved.config
|
156
|
+
end
|
157
|
+
|
158
|
+
url = url.dup
|
159
|
+
|
160
|
+
Class.new(PgHero::Connection) do
|
161
|
+
def self.name
|
162
|
+
"PgHero::Connection::Database#{object_id}"
|
163
|
+
end
|
164
|
+
|
165
|
+
case url
|
166
|
+
when String
|
167
|
+
url = "#{url}#{url.include?("?") ? "&" : "?"}connect_timeout=5" unless url.include?("connect_timeout=")
|
168
|
+
when Hash
|
169
|
+
url[:connect_timeout] ||= 5
|
170
|
+
end
|
171
|
+
establish_connection url if url
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|