pghero 2.5.0 → 2.6.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 +11 -0
- data/lib/generators/pghero/templates/config.yml.tt +3 -3
- data/lib/pghero.rb +8 -3
- data/lib/pghero/database.rb +3 -2
- data/lib/pghero/methods/explain.rb +1 -1
- data/lib/pghero/methods/query_stats.rb +4 -2
- data/lib/pghero/methods/system.rb +55 -22
- data/lib/pghero/methods/users.rb +4 -0
- data/lib/pghero/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7334e516c44c91605f37b8fd22b57a46b147cfe4191796e48624e8a15b45ca0
|
4
|
+
data.tar.gz: 1235c0626b5a7f2061f2bcfce062d4716f3de7a5c198aeed489eaf9d0c7549c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52944fa0d91437a0fd42dc4e8705a4f833a3d60937748f1cbffd03aa9471af8ce2e30db7645c419590696dcbbe0e700941b838216ae3da7a5bc211f0067188a5
|
7
|
+
data.tar.gz: 8ff19581f7245ebc804ef49af131c9f6eca5ed8110b878ccb8e1b15cf94c2951d88e4329e1565145e03b21c63e3798be400dc74b2655c7489dca75ba9ca4aec5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## 2.6.0 (2020-07-09)
|
2
|
+
|
3
|
+
- Added support for Postgres 13 beta 2
|
4
|
+
- Added support for non-integer explain timeout
|
5
|
+
|
6
|
+
## 2.5.1 (2020-06-23)
|
7
|
+
|
8
|
+
- Added support for `google-cloud-monitoring` >= 1
|
9
|
+
- Added support for `google-cloud-monitoring-v3`
|
10
|
+
- Fixed system stats not showing up in Rails 6 with environment variables
|
11
|
+
|
1
12
|
## 2.5.0 (2020-05-24)
|
2
13
|
|
3
14
|
- Added system stats for Google Cloud SQL and Azure Database
|
@@ -32,14 +32,14 @@ databases:
|
|
32
32
|
|
33
33
|
# Basic authentication
|
34
34
|
# username: admin
|
35
|
-
# password:
|
35
|
+
# password: <%%= ENV["PGHERO_PASSWORD"] %>
|
36
36
|
|
37
37
|
# Stats database URL (defaults to app database)
|
38
38
|
# stats_database_url: <%%= ENV["PGHERO_STATS_DATABASE_URL"] %>
|
39
39
|
|
40
40
|
# AWS configuration (defaults to app AWS config)
|
41
|
-
# aws_access_key_id:
|
42
|
-
# aws_secret_access_key:
|
41
|
+
# aws_access_key_id: <%%= ENV["AWS_ACCESS_KEY_ID"] %>
|
42
|
+
# aws_secret_access_key: <%%= ENV["AWS_SECRET_ACCESS_KEY"] %>
|
43
43
|
# aws_region: us-east-1
|
44
44
|
|
45
45
|
# Filter data from queries (experimental)
|
data/lib/pghero.rb
CHANGED
@@ -43,7 +43,7 @@ module PgHero
|
|
43
43
|
self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
|
44
44
|
self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
|
45
45
|
self.slow_query_calls = (ENV["PGHERO_SLOW_QUERY_CALLS"] || 100).to_i
|
46
|
-
self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).
|
46
|
+
self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).to_f
|
47
47
|
self.total_connections_threshold = (ENV["PGHERO_TOTAL_CONNECTIONS_THRESHOLD"] || 500).to_i
|
48
48
|
self.cache_hit_rate_threshold = 99
|
49
49
|
self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
@@ -119,11 +119,16 @@ module PgHero
|
|
119
119
|
|
120
120
|
if databases.empty?
|
121
121
|
databases["primary"] = {
|
122
|
-
"url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config
|
122
|
+
"url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
if databases.size == 1
|
127
|
+
databases.values.first.merge!(
|
123
128
|
"db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"],
|
124
129
|
"gcp_database_id" => ENV["PGHERO_GCP_DATABASE_ID"],
|
125
130
|
"azure_resource_id" => ENV["PGHERO_AZURE_RESOURCE_ID"]
|
126
|
-
|
131
|
+
)
|
127
132
|
end
|
128
133
|
|
129
134
|
{
|
data/lib/pghero/database.rb
CHANGED
@@ -55,15 +55,16 @@ module PgHero
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def explain_timeout_sec
|
58
|
-
(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
|
59
59
|
end
|
60
60
|
|
61
61
|
def long_running_query_sec
|
62
62
|
(config["long_running_query_sec"] || PgHero.config["long_running_query_sec"] || PgHero.long_running_query_sec).to_i
|
63
63
|
end
|
64
64
|
|
65
|
+
# defaults to 100 megabytes
|
65
66
|
def index_bloat_bytes
|
66
|
-
(config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] ||
|
67
|
+
(config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] || 104857600).to_i
|
67
68
|
end
|
68
69
|
|
69
70
|
def aws_access_key_id
|
@@ -6,7 +6,7 @@ module PgHero
|
|
6
6
|
explanation = nil
|
7
7
|
|
8
8
|
# use transaction for safety
|
9
|
-
with_transaction(statement_timeout: (explain_timeout_sec * 1000), rollback: true) do
|
9
|
+
with_transaction(statement_timeout: (explain_timeout_sec * 1000).round, rollback: true) do
|
10
10
|
if (sql.sub(/;\z/, "").include?(";") || sql.upcase.include?("COMMIT")) && !explain_safe?
|
11
11
|
raise ActiveRecord::StatementInvalid, "Unsafe statement"
|
12
12
|
end
|
@@ -166,14 +166,15 @@ module PgHero
|
|
166
166
|
if query_stats_enabled?
|
167
167
|
limit ||= 100
|
168
168
|
sort ||= "total_minutes"
|
169
|
+
total_time = server_version_num >= 130000 ? "(total_plan_time + total_exec_time)" : "total_time"
|
169
170
|
query = <<-SQL
|
170
171
|
WITH query_stats AS (
|
171
172
|
SELECT
|
172
173
|
LEFT(query, 10000) AS query,
|
173
174
|
#{supports_query_hash? ? "queryid" : "md5(query)"} AS query_hash,
|
174
175
|
rolname AS user,
|
175
|
-
(total_time / 1000 / 60) AS total_minutes,
|
176
|
-
(total_time / calls) AS average_time,
|
176
|
+
(#{total_time} / 1000 / 60) AS total_minutes,
|
177
|
+
(#{total_time} / calls) AS average_time,
|
177
178
|
calls
|
178
179
|
FROM
|
179
180
|
pg_stat_statements
|
@@ -182,6 +183,7 @@ module PgHero
|
|
182
183
|
INNER JOIN
|
183
184
|
pg_roles ON pg_roles.oid = pg_stat_statements.userid
|
184
185
|
WHERE
|
186
|
+
calls > 0 AND
|
185
187
|
pg_database.datname = #{database ? quote(database) : "current_database()"}
|
186
188
|
#{query_hash ? "AND queryid = #{quote(query_hash)}" : nil}
|
187
189
|
)
|
@@ -5,7 +5,7 @@ module PgHero
|
|
5
5
|
!system_stats_provider.nil?
|
6
6
|
end
|
7
7
|
|
8
|
-
# TODO
|
8
|
+
# TODO remove defined checks in 3.0
|
9
9
|
def system_stats_provider
|
10
10
|
if aws_db_instance_identifier && (defined?(Aws) || defined?(AWS))
|
11
11
|
:aws
|
@@ -133,7 +133,7 @@ module PgHero
|
|
133
133
|
private
|
134
134
|
|
135
135
|
def gcp_stats(metric_name, duration: nil, period: nil, offset: nil, series: false)
|
136
|
-
require "google/cloud/monitoring"
|
136
|
+
require "google/cloud/monitoring/v3"
|
137
137
|
|
138
138
|
# TODO DRY with RDS stats
|
139
139
|
duration = (duration || 1.hour).to_i
|
@@ -142,30 +142,63 @@ module PgHero
|
|
142
142
|
end_time = Time.at(((Time.now - offset).to_f / period).ceil * period)
|
143
143
|
start_time = end_time - duration
|
144
144
|
|
145
|
-
client = Google::Cloud::Monitoring::Metric.new
|
146
|
-
|
147
|
-
interval = Google::Monitoring::V3::TimeInterval.new
|
148
|
-
interval.end_time = Google::Protobuf::Timestamp.new(seconds: end_time.to_i)
|
149
|
-
# subtract period to make sure we get first data point
|
150
|
-
interval.start_time = Google::Protobuf::Timestamp.new(seconds: (start_time - period).to_i)
|
151
|
-
|
152
|
-
aggregation = Google::Monitoring::V3::Aggregation.new
|
153
|
-
# may be better to use ALIGN_NEXT_OLDER for space stats to show most recent data point
|
154
|
-
# stick with average for now to match AWS
|
155
|
-
aggregation.per_series_aligner = Google::Monitoring::V3::Aggregation::Aligner::ALIGN_MEAN
|
156
|
-
aggregation.alignment_period = period
|
157
|
-
|
158
145
|
# validate input since we need to interpolate below
|
159
146
|
raise Error, "Invalid metric name" unless metric_name =~ /\A[a-z\/_]+\z/i
|
160
147
|
raise Error, "Invalid database id" unless gcp_database_id =~ /\A[a-z\-:]+\z/i
|
161
148
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
149
|
+
# we handle three situations:
|
150
|
+
# 1. google-cloud-monitoring-v3
|
151
|
+
# 2. google-cloud-monitoring >= 1
|
152
|
+
# 3. google-cloud-monitoring < 1
|
153
|
+
|
154
|
+
# for situations 1 and 2
|
155
|
+
# Google::Cloud::Monitoring.metric_service is documented
|
156
|
+
# but doesn't work for situation 1
|
157
|
+
if defined?(Google::Cloud::Monitoring::V3::MetricService::Client)
|
158
|
+
client = Google::Cloud::Monitoring::V3::MetricService::Client.new
|
159
|
+
|
160
|
+
interval = Google::Cloud::Monitoring::V3::TimeInterval.new
|
161
|
+
interval.end_time = Google::Protobuf::Timestamp.new(seconds: end_time.to_i)
|
162
|
+
# subtract period to make sure we get first data point
|
163
|
+
interval.start_time = Google::Protobuf::Timestamp.new(seconds: (start_time - period).to_i)
|
164
|
+
|
165
|
+
aggregation = Google::Cloud::Monitoring::V3::Aggregation.new
|
166
|
+
# may be better to use ALIGN_NEXT_OLDER for space stats to show most recent data point
|
167
|
+
# stick with average for now to match AWS
|
168
|
+
aggregation.per_series_aligner = Google::Cloud::Monitoring::V3::Aggregation::Aligner::ALIGN_MEAN
|
169
|
+
aggregation.alignment_period = period
|
170
|
+
|
171
|
+
results = client.list_time_series({
|
172
|
+
name: "projects/#{gcp_database_id.split(":").first}",
|
173
|
+
filter: "metric.type = \"cloudsql.googleapis.com/database/#{metric_name}\" AND resource.label.database_id = \"#{gcp_database_id}\"",
|
174
|
+
interval: interval,
|
175
|
+
view: Google::Cloud::Monitoring::V3::ListTimeSeriesRequest::TimeSeriesView::FULL,
|
176
|
+
aggregation: aggregation
|
177
|
+
})
|
178
|
+
else
|
179
|
+
require "google/cloud/monitoring"
|
180
|
+
|
181
|
+
client = Google::Cloud::Monitoring::Metric.new
|
182
|
+
|
183
|
+
interval = Google::Monitoring::V3::TimeInterval.new
|
184
|
+
interval.end_time = Google::Protobuf::Timestamp.new(seconds: end_time.to_i)
|
185
|
+
# subtract period to make sure we get first data point
|
186
|
+
interval.start_time = Google::Protobuf::Timestamp.new(seconds: (start_time - period).to_i)
|
187
|
+
|
188
|
+
aggregation = Google::Monitoring::V3::Aggregation.new
|
189
|
+
# may be better to use ALIGN_NEXT_OLDER for space stats to show most recent data point
|
190
|
+
# stick with average for now to match AWS
|
191
|
+
aggregation.per_series_aligner = Google::Monitoring::V3::Aggregation::Aligner::ALIGN_MEAN
|
192
|
+
aggregation.alignment_period = period
|
193
|
+
|
194
|
+
results = client.list_time_series(
|
195
|
+
"projects/#{gcp_database_id.split(":").first}",
|
196
|
+
"metric.type = \"cloudsql.googleapis.com/database/#{metric_name}\" AND resource.label.database_id = \"#{gcp_database_id}\"",
|
197
|
+
interval,
|
198
|
+
Google::Monitoring::V3::ListTimeSeriesRequest::TimeSeriesView::FULL,
|
199
|
+
aggregation: aggregation
|
200
|
+
)
|
201
|
+
end
|
169
202
|
|
170
203
|
data = {}
|
171
204
|
result = results.first
|
data/lib/pghero/methods/users.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module PgHero
|
2
2
|
module Methods
|
3
3
|
module Users
|
4
|
+
# documented as unsafe to pass user input
|
5
|
+
# TODO quote in 3.0, but still not officially supported
|
4
6
|
def create_user(user, password: nil, schema: "public", database: nil, readonly: false, tables: nil)
|
5
7
|
password ||= random_password
|
6
8
|
database ||= connection_model.connection_config[:database]
|
@@ -39,6 +41,8 @@ module PgHero
|
|
39
41
|
{password: password}
|
40
42
|
end
|
41
43
|
|
44
|
+
# documented as unsafe to pass user input
|
45
|
+
# TODO quote in 3.0, but still not officially supported
|
42
46
|
def drop_user(user, schema: "public", database: nil)
|
43
47
|
database ||= connection_model.connection_config[:database]
|
44
48
|
|
data/lib/pghero/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pghero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|