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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 252b8c1bb7580d67b149ef865f97b99a89fbd9aecb640a92f5ebf8a532d7c10b
4
- data.tar.gz: 87a9df921265875867cb9d9b0dcd92fa6d6855075818129a8b5bf47337277354
3
+ metadata.gz: e7334e516c44c91605f37b8fd22b57a46b147cfe4191796e48624e8a15b45ca0
4
+ data.tar.gz: 1235c0626b5a7f2061f2bcfce062d4716f3de7a5c198aeed489eaf9d0c7549c0
5
5
  SHA512:
6
- metadata.gz: 9b1f75f5ef19ee10da7ee815aca9ee7933fa89ae78046e495417f7c863f383172fba28bfb00605ac8810a2bb5a9895ae478a3f182ba36204080151f84ad9e2a6
7
- data.tar.gz: 6fc630bd5f46fbecb5b6194d58726397c8f28a812e7ceabe12784ed6892bd7f7609646cba22e57c34ab817be40b37e251384f5ba23a32e82199db7620f4dc009
6
+ metadata.gz: 52944fa0d91437a0fd42dc4e8705a4f833a3d60937748f1cbffd03aa9471af8ce2e30db7645c419590696dcbbe0e700941b838216ae3da7a5bc211f0067188a5
7
+ data.tar.gz: 8ff19581f7245ebc804ef49af131c9f6eca5ed8110b878ccb8e1b15cf94c2951d88e4329e1565145e03b21c63e3798be400dc74b2655c7489dca75ba9ca4aec5
@@ -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: secret
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)
@@ -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).to_i
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
  {
@@ -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).to_i
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"] || 100.megabytes).to_i
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 require AWS 2+ automatically
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
- results = client.list_time_series(
163
- "projects/#{gcp_database_id.split(":").first}",
164
- "metric.type = \"cloudsql.googleapis.com/database/#{metric_name}\" AND resource.label.database_id = \"#{gcp_database_id}\"",
165
- interval,
166
- Google::Monitoring::V3::ListTimeSeriesRequest::TimeSeriesView::FULL,
167
- aggregation: aggregation
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
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module PgHero
2
- VERSION = "2.5.0"
2
+ VERSION = "2.6.0"
3
3
  end
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.5.0
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-05-24 00:00:00.000000000 Z
11
+ date: 2020-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord