pghero 2.2.1 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +100 -53
- data/README.md +20 -8
- data/app/assets/javascripts/pghero/Chart.bundle.js +16260 -15580
- data/app/assets/javascripts/pghero/application.js +8 -7
- data/app/assets/javascripts/pghero/chartkick.js +1973 -1325
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -2
- data/app/assets/javascripts/pghero/jquery.js +3605 -4015
- data/app/assets/javascripts/pghero/nouislider.js +2479 -0
- data/app/assets/stylesheets/pghero/application.css +1 -1
- data/app/assets/stylesheets/pghero/nouislider.css +299 -0
- data/app/controllers/pg_hero/home_controller.rb +97 -42
- data/app/helpers/pg_hero/home_helper.rb +11 -0
- data/app/views/pg_hero/home/_live_queries_table.html.erb +14 -3
- data/app/views/pg_hero/home/connections.html.erb +9 -0
- data/app/views/pg_hero/home/index.html.erb +49 -10
- data/app/views/pg_hero/home/live_queries.html.erb +1 -1
- data/app/views/pg_hero/home/maintenance.html.erb +16 -2
- data/app/views/pg_hero/home/relation_space.html.erb +2 -2
- data/app/views/pg_hero/home/show_query.html.erb +4 -5
- data/app/views/pg_hero/home/space.html.erb +3 -3
- data/app/views/pg_hero/home/system.html.erb +4 -4
- data/app/views/pg_hero/home/tune.html.erb +2 -1
- data/lib/generators/pghero/config_generator.rb +1 -1
- data/lib/generators/pghero/query_stats_generator.rb +3 -20
- data/lib/generators/pghero/space_stats_generator.rb +3 -20
- data/lib/generators/pghero/templates/config.yml.tt +21 -1
- data/lib/pghero.rb +82 -17
- data/lib/pghero/database.rb +104 -19
- data/lib/pghero/methods/basic.rb +34 -25
- data/lib/pghero/methods/connections.rb +35 -0
- data/lib/pghero/methods/constraints.rb +30 -0
- data/lib/pghero/methods/explain.rb +1 -1
- data/lib/pghero/methods/indexes.rb +1 -1
- data/lib/pghero/methods/maintenance.rb +3 -1
- data/lib/pghero/methods/queries.rb +7 -3
- data/lib/pghero/methods/query_stats.rb +93 -25
- data/lib/pghero/methods/sequences.rb +1 -1
- data/lib/pghero/methods/space.rb +4 -0
- data/lib/pghero/methods/suggested_indexes.rb +1 -1
- data/lib/pghero/methods/system.rb +219 -23
- data/lib/pghero/methods/users.rb +4 -0
- data/lib/pghero/query_stats.rb +1 -3
- data/lib/pghero/space_stats.rb +5 -0
- data/lib/pghero/stats.rb +6 -0
- data/lib/pghero/version.rb +1 -1
- data/lib/tasks/pghero.rake +10 -4
- metadata +15 -12
- data/app/assets/javascripts/pghero/jquery.nouislider.min.js +0 -31
- data/app/assets/stylesheets/pghero/jquery.nouislider.css +0 -165
data/lib/pghero.rb
CHANGED
@@ -5,6 +5,7 @@ require "forwardable"
|
|
5
5
|
# methods
|
6
6
|
require "pghero/methods/basic"
|
7
7
|
require "pghero/methods/connections"
|
8
|
+
require "pghero/methods/constraints"
|
8
9
|
require "pghero/methods/explain"
|
9
10
|
require "pghero/methods/indexes"
|
10
11
|
require "pghero/methods/kill"
|
@@ -26,32 +27,37 @@ require "pghero/version"
|
|
26
27
|
|
27
28
|
module PgHero
|
28
29
|
autoload :Connection, "pghero/connection"
|
30
|
+
autoload :Stats, "pghero/stats"
|
29
31
|
autoload :QueryStats, "pghero/query_stats"
|
32
|
+
autoload :SpaceStats, "pghero/space_stats"
|
30
33
|
|
31
34
|
class Error < StandardError; end
|
32
35
|
class NotEnabled < Error; end
|
33
36
|
|
37
|
+
MUTEX = Mutex.new
|
38
|
+
|
34
39
|
# settings
|
35
40
|
class << self
|
36
|
-
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
|
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
|
37
42
|
end
|
38
43
|
self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
|
39
44
|
self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
|
40
45
|
self.slow_query_calls = (ENV["PGHERO_SLOW_QUERY_CALLS"] || 100).to_i
|
41
|
-
self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).
|
46
|
+
self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).to_f
|
42
47
|
self.total_connections_threshold = (ENV["PGHERO_TOTAL_CONNECTIONS_THRESHOLD"] || 500).to_i
|
43
48
|
self.cache_hit_rate_threshold = 99
|
44
49
|
self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
45
50
|
self.show_migrations = true
|
46
51
|
self.config_path = ENV["PGHERO_CONFIG_PATH"] || "config/pghero.yml"
|
52
|
+
self.filter_data = ENV["PGHERO_FILTER_DATA"].to_s.size > 0
|
47
53
|
|
48
54
|
class << self
|
49
55
|
extend Forwardable
|
50
56
|
def_delegators :primary_database, :access_key_id, :analyze, :analyze_tables, :autoindex, :autovacuum_danger,
|
51
|
-
:best_index, :blocked_queries, :connection_sources, :connection_states, :connection_stats,
|
57
|
+
:best_index, :blocked_queries, :connections, :connection_sources, :connection_states, :connection_stats,
|
52
58
|
:cpu_usage, :create_user, :database_size, :db_instance_identifier, :disable_query_stats, :drop_user,
|
53
59
|
:duplicate_indexes, :enable_query_stats, :explain, :historical_query_stats_enabled?, :index_caching,
|
54
|
-
:index_hit_rate, :index_usage, :indexes, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
|
60
|
+
:index_hit_rate, :index_usage, :indexes, :invalid_constraints, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
|
55
61
|
:last_stats_reset_time, :long_running_queries, :maintenance_info, :missing_indexes, :query_stats,
|
56
62
|
:query_stats_available?, :query_stats_enabled?, :query_stats_extension_enabled?, :query_stats_readable?,
|
57
63
|
:rds_stats, :read_iops_stats, :region, :relation_sizes, :replica?, :replication_lag, :replication_lag_stats,
|
@@ -68,6 +74,22 @@ module PgHero
|
|
68
74
|
@time_zone || Time.zone
|
69
75
|
end
|
70
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
|
+
|
71
93
|
def config
|
72
94
|
@config ||= begin
|
73
95
|
require "erb"
|
@@ -87,26 +109,49 @@ module PgHero
|
|
87
109
|
elsif config_file_exists
|
88
110
|
raise "Invalid config file"
|
89
111
|
else
|
90
|
-
{
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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.spec_name] = {"spec" => db.spec_name}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if databases.empty?
|
121
|
+
databases["primary"] = {
|
122
|
+
"url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config
|
96
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
|
97
136
|
}
|
98
137
|
end
|
99
138
|
end
|
100
139
|
end
|
101
140
|
|
141
|
+
# ensure we only have one copy of databases
|
142
|
+
# so there's only one connection pool per database
|
102
143
|
def databases
|
103
|
-
@databases
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
109
152
|
end
|
153
|
+
|
154
|
+
@databases
|
110
155
|
end
|
111
156
|
|
112
157
|
def primary_database
|
@@ -137,7 +182,7 @@ module PgHero
|
|
137
182
|
|
138
183
|
def autoindex_all(create: false, verbose: true)
|
139
184
|
each_database do |database|
|
140
|
-
puts "Autoindexing #{database}..." if verbose
|
185
|
+
puts "Autoindexing #{database.id}..." if verbose
|
141
186
|
database.autoindex(create: create)
|
142
187
|
end
|
143
188
|
end
|
@@ -146,6 +191,26 @@ module PgHero
|
|
146
191
|
ActiveSupport::NumberHelper.number_to_human_size(value, precision: 3)
|
147
192
|
end
|
148
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
|
+
|
149
214
|
private
|
150
215
|
|
151
216
|
def each_database
|
data/lib/pghero/database.rb
CHANGED
@@ -2,6 +2,7 @@ module PgHero
|
|
2
2
|
class Database
|
3
3
|
include Methods::Basic
|
4
4
|
include Methods::Connections
|
5
|
+
include Methods::Constraints
|
5
6
|
include Methods::Explain
|
6
7
|
include Methods::Indexes
|
7
8
|
include Methods::Kill
|
@@ -22,16 +23,17 @@ module PgHero
|
|
22
23
|
def initialize(id, config)
|
23
24
|
@id = id
|
24
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
|
25
31
|
end
|
26
32
|
|
27
33
|
def name
|
28
34
|
@name ||= @config["name"] || id.titleize
|
29
35
|
end
|
30
36
|
|
31
|
-
def db_instance_identifier
|
32
|
-
@db_instance_identifier ||= @config["db_instance_identifier"]
|
33
|
-
end
|
34
|
-
|
35
37
|
def capture_query_stats?
|
36
38
|
config["capture_query_stats"] != false
|
37
39
|
end
|
@@ -53,35 +55,118 @@ module PgHero
|
|
53
55
|
end
|
54
56
|
|
55
57
|
def explain_timeout_sec
|
56
|
-
(config["explain_timeout_sec"] || PgHero.config["explain_timeout_sec"] || PgHero.explain_timeout_sec).
|
58
|
+
(config["explain_timeout_sec"] || PgHero.config["explain_timeout_sec"] || PgHero.explain_timeout_sec).to_f
|
57
59
|
end
|
58
60
|
|
59
61
|
def long_running_query_sec
|
60
62
|
(config["long_running_query_sec"] || PgHero.config["long_running_query_sec"] || PgHero.long_running_query_sec).to_i
|
61
63
|
end
|
62
64
|
|
65
|
+
# defaults to 100 megabytes
|
63
66
|
def index_bloat_bytes
|
64
|
-
(config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] ||
|
67
|
+
(config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] || 104857600).to_i
|
65
68
|
end
|
66
69
|
|
67
|
-
|
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
|
68
73
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
75
107
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
108
|
+
|
109
|
+
if @filter_data
|
110
|
+
begin
|
111
|
+
require "pg_query"
|
112
|
+
rescue LoadError
|
113
|
+
raise Error, "pg_query required for filter_data"
|
81
114
|
end
|
82
|
-
establish_connection url if url
|
83
115
|
end
|
84
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
|
+
resolved = ActiveRecord::Base.configurations.configs_for(env_name: PgHero.env, spec_name: config["spec"], include_replicas: true)
|
153
|
+
raise Error, "Spec not found: #{config["spec"]}" unless resolved
|
154
|
+
url = resolved.config
|
155
|
+
end
|
156
|
+
|
157
|
+
Class.new(PgHero::Connection) do
|
158
|
+
def self.name
|
159
|
+
"PgHero::Connection::Database#{object_id}"
|
160
|
+
end
|
161
|
+
|
162
|
+
case url
|
163
|
+
when String
|
164
|
+
url = "#{url}#{url.include?("?") ? "&" : "?"}connect_timeout=5" unless url.include?("connect_timeout=")
|
165
|
+
when Hash
|
166
|
+
url[:connect_timeout] ||= 5
|
167
|
+
end
|
168
|
+
establish_connection url if url
|
169
|
+
end
|
85
170
|
end
|
86
171
|
end
|
87
172
|
end
|
data/lib/pghero/methods/basic.rb
CHANGED
@@ -18,6 +18,10 @@ module PgHero
|
|
18
18
|
select_one("SELECT current_database()")
|
19
19
|
end
|
20
20
|
|
21
|
+
def current_user
|
22
|
+
select_one("SELECT current_user")
|
23
|
+
end
|
24
|
+
|
21
25
|
def server_version
|
22
26
|
@server_version ||= select_one("SHOW server_version")
|
23
27
|
end
|
@@ -32,14 +36,34 @@ module PgHero
|
|
32
36
|
|
33
37
|
private
|
34
38
|
|
35
|
-
def select_all(sql, conn
|
39
|
+
def select_all(sql, conn: nil, query_columns: [])
|
36
40
|
conn ||= connection
|
37
41
|
# squish for logs
|
38
42
|
retries = 0
|
39
43
|
begin
|
40
44
|
result = conn.select_all(add_source(squish(sql)))
|
41
|
-
|
42
|
-
|
45
|
+
result = result.map { |row| Hash[row.map { |col, val| [col.to_sym, result.column_types[col].send(:cast_value, val)] }] }
|
46
|
+
if filter_data
|
47
|
+
query_columns.each do |column|
|
48
|
+
result.each do |row|
|
49
|
+
begin
|
50
|
+
row[column] = PgQuery.normalize(row[column])
|
51
|
+
rescue PgQuery::ParseError
|
52
|
+
# try replacing "interval $1" with "$1::interval"
|
53
|
+
# see https://github.com/lfittl/pg_query/issues/169 for more info
|
54
|
+
# this is not ideal since it changes the query slightly
|
55
|
+
# we could skip normalization
|
56
|
+
# but this has a very small chance of data leakage
|
57
|
+
begin
|
58
|
+
row[column] = PgQuery.normalize(row[column].gsub(/\binterval\s+(\$\d+)\b/i, "\\1::interval"))
|
59
|
+
rescue PgQuery::ParseError
|
60
|
+
row[column] = "<unable to filter data>"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
result
|
43
67
|
rescue ActiveRecord::StatementInvalid => e
|
44
68
|
# fix for random internal errors
|
45
69
|
if e.message.include?("PG::InternalError") && retries < 2
|
@@ -52,8 +76,8 @@ module PgHero
|
|
52
76
|
end
|
53
77
|
end
|
54
78
|
|
55
|
-
def select_all_stats(sql)
|
56
|
-
select_all(sql, stats_connection)
|
79
|
+
def select_all_stats(sql, **options)
|
80
|
+
select_all(sql, **options, conn: stats_connection)
|
57
81
|
end
|
58
82
|
|
59
83
|
def select_all_size(sql)
|
@@ -64,12 +88,12 @@ module PgHero
|
|
64
88
|
result
|
65
89
|
end
|
66
90
|
|
67
|
-
def select_one(sql, conn
|
68
|
-
select_all(sql, conn).first.values.first
|
91
|
+
def select_one(sql, conn: nil)
|
92
|
+
select_all(sql, conn: conn).first.values.first
|
69
93
|
end
|
70
94
|
|
71
95
|
def select_one_stats(sql)
|
72
|
-
select_one(sql, stats_connection)
|
96
|
+
select_one(sql, conn: stats_connection)
|
73
97
|
end
|
74
98
|
|
75
99
|
def execute(sql)
|
@@ -81,7 +105,7 @@ module PgHero
|
|
81
105
|
end
|
82
106
|
|
83
107
|
def stats_connection
|
84
|
-
::PgHero::
|
108
|
+
::PgHero::Stats.connection
|
85
109
|
end
|
86
110
|
|
87
111
|
def insert_stats(table, columns, values)
|
@@ -125,22 +149,7 @@ module PgHero
|
|
125
149
|
end
|
126
150
|
|
127
151
|
def table_exists?(table)
|
128
|
-
|
129
|
-
select_one_stats(<<-SQL
|
130
|
-
SELECT EXISTS (
|
131
|
-
SELECT
|
132
|
-
1
|
133
|
-
FROM
|
134
|
-
pg_catalog.pg_class c
|
135
|
-
INNER JOIN
|
136
|
-
pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
137
|
-
WHERE
|
138
|
-
n.nspname = 'public'
|
139
|
-
AND c.relname = #{quote(table)}
|
140
|
-
AND c.relkind = 'r'
|
141
|
-
)
|
142
|
-
SQL
|
143
|
-
)
|
152
|
+
stats_connection.table_exists?(table)
|
144
153
|
end
|
145
154
|
end
|
146
155
|
end
|
@@ -1,6 +1,41 @@
|
|
1
1
|
module PgHero
|
2
2
|
module Methods
|
3
3
|
module Connections
|
4
|
+
def connections
|
5
|
+
if server_version_num >= 90500
|
6
|
+
select_all <<-SQL
|
7
|
+
SELECT
|
8
|
+
pg_stat_activity.pid,
|
9
|
+
datname AS database,
|
10
|
+
usename AS user,
|
11
|
+
application_name AS source,
|
12
|
+
client_addr AS ip,
|
13
|
+
state,
|
14
|
+
ssl
|
15
|
+
FROM
|
16
|
+
pg_stat_activity
|
17
|
+
LEFT JOIN
|
18
|
+
pg_stat_ssl ON pg_stat_activity.pid = pg_stat_ssl.pid
|
19
|
+
ORDER BY
|
20
|
+
pg_stat_activity.pid
|
21
|
+
SQL
|
22
|
+
else
|
23
|
+
select_all <<-SQL
|
24
|
+
SELECT
|
25
|
+
pid,
|
26
|
+
datname AS database,
|
27
|
+
usename AS user,
|
28
|
+
application_name AS source,
|
29
|
+
client_addr AS ip,
|
30
|
+
state
|
31
|
+
FROM
|
32
|
+
pg_stat_activity
|
33
|
+
ORDER BY
|
34
|
+
pid
|
35
|
+
SQL
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
4
39
|
def total_connections
|
5
40
|
select_one("SELECT COUNT(*) FROM pg_stat_activity")
|
6
41
|
end
|