pghero 1.2.2 → 1.2.3
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/.travis.yml +11 -0
- data/CHANGELOG.md +6 -0
- data/README.md +1 -1
- data/app/controllers/pg_hero/home_controller.rb +15 -4
- data/app/views/layouts/pg_hero/application.html.erb +4 -4
- data/app/views/pg_hero/home/explain.html.erb +1 -1
- data/app/views/pg_hero/home/index_usage.html.erb +6 -1
- data/app/views/pg_hero/home/maintenance.html.erb +6 -1
- data/app/views/pg_hero/home/space.html.erb +5 -3
- data/lib/pghero.rb +35 -1243
- data/lib/pghero/connection.rb +5 -0
- data/lib/pghero/database.rb +12 -3
- data/lib/pghero/methods/basic.rb +104 -0
- data/lib/pghero/methods/connections.rb +49 -0
- data/lib/pghero/methods/databases.rb +39 -0
- data/lib/pghero/methods/explain.rb +29 -0
- data/lib/pghero/methods/indexes.rb +154 -0
- data/lib/pghero/methods/kill.rb +27 -0
- data/lib/pghero/methods/maintenance.rb +61 -0
- data/lib/pghero/methods/queries.rb +73 -0
- data/lib/pghero/methods/query_stats.rb +188 -0
- data/lib/pghero/methods/replica.rb +22 -0
- data/lib/pghero/methods/space.rb +30 -0
- data/lib/pghero/methods/suggested_indexes.rb +322 -0
- data/lib/pghero/methods/system.rb +70 -0
- data/lib/pghero/methods/tables.rb +68 -0
- data/lib/pghero/methods/users.rb +85 -0
- data/lib/pghero/query_stats.rb +7 -0
- data/lib/pghero/version.rb +1 -1
- data/lib/{pghero/tasks.rb → tasks/pghero.rake} +0 -2
- data/test/suggested_indexes_test.rb +3 -2
- data/test/test_helper.rb +1 -1
- metadata +22 -10
- data/test/gemfiles/activerecord31.gemfile +0 -6
- data/test/gemfiles/activerecord32.gemfile +0 -6
- data/test/gemfiles/activerecord40.gemfile +0 -6
@@ -0,0 +1,70 @@
|
|
1
|
+
module PgHero
|
2
|
+
module Methods
|
3
|
+
module System
|
4
|
+
def cpu_usage
|
5
|
+
rds_stats("CPUUtilization")
|
6
|
+
end
|
7
|
+
|
8
|
+
def connection_stats
|
9
|
+
rds_stats("DatabaseConnections")
|
10
|
+
end
|
11
|
+
|
12
|
+
def replication_lag_stats
|
13
|
+
rds_stats("ReplicaLag")
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_iops_stats
|
17
|
+
rds_stats("ReadIOPS")
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_iops_stats
|
21
|
+
rds_stats("WriteIOPS")
|
22
|
+
end
|
23
|
+
|
24
|
+
def rds_stats(metric_name)
|
25
|
+
if system_stats_enabled?
|
26
|
+
client =
|
27
|
+
if defined?(Aws)
|
28
|
+
Aws::CloudWatch::Client.new(access_key_id: access_key_id, secret_access_key: secret_access_key)
|
29
|
+
else
|
30
|
+
AWS::CloudWatch.new(access_key_id: access_key_id, secret_access_key: secret_access_key).client
|
31
|
+
end
|
32
|
+
|
33
|
+
now = Time.now
|
34
|
+
resp = client.get_metric_statistics(
|
35
|
+
namespace: "AWS/RDS",
|
36
|
+
metric_name: metric_name,
|
37
|
+
dimensions: [{name: "DBInstanceIdentifier", value: db_instance_identifier}],
|
38
|
+
start_time: (now - 1 * 3600).iso8601,
|
39
|
+
end_time: now.iso8601,
|
40
|
+
period: 60,
|
41
|
+
statistics: ["Average"]
|
42
|
+
)
|
43
|
+
data = {}
|
44
|
+
resp[:datapoints].sort_by { |d| d[:timestamp] }.each do |d|
|
45
|
+
data[d[:timestamp]] = d[:average]
|
46
|
+
end
|
47
|
+
data
|
48
|
+
else
|
49
|
+
{}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def system_stats_enabled?
|
54
|
+
!!((defined?(Aws) || defined?(AWS)) && access_key_id && secret_access_key && db_instance_identifier)
|
55
|
+
end
|
56
|
+
|
57
|
+
def access_key_id
|
58
|
+
ENV["PGHERO_ACCESS_KEY_ID"] || ENV["AWS_ACCESS_KEY_ID"]
|
59
|
+
end
|
60
|
+
|
61
|
+
def secret_access_key
|
62
|
+
ENV["PGHERO_SECRET_ACCESS_KEY"] || ENV["AWS_SECRET_ACCESS_KEY"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def db_instance_identifier
|
66
|
+
databases[current_database].db_instance_identifier
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module PgHero
|
2
|
+
module Methods
|
3
|
+
module Tables
|
4
|
+
def table_hit_rate
|
5
|
+
select_all(<<-SQL
|
6
|
+
SELECT
|
7
|
+
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) AS rate
|
8
|
+
FROM
|
9
|
+
pg_statio_user_tables
|
10
|
+
SQL
|
11
|
+
).first["rate"].to_f
|
12
|
+
end
|
13
|
+
|
14
|
+
def table_caching
|
15
|
+
select_all <<-SQL
|
16
|
+
SELECT
|
17
|
+
relname AS table,
|
18
|
+
CASE WHEN heap_blks_hit + heap_blks_read = 0 THEN
|
19
|
+
0
|
20
|
+
ELSE
|
21
|
+
ROUND(1.0 * heap_blks_hit / (heap_blks_hit + heap_blks_read), 2)
|
22
|
+
END AS hit_rate
|
23
|
+
FROM
|
24
|
+
pg_statio_user_tables
|
25
|
+
ORDER BY
|
26
|
+
2 DESC, 1
|
27
|
+
SQL
|
28
|
+
end
|
29
|
+
|
30
|
+
def unused_tables
|
31
|
+
select_all <<-SQL
|
32
|
+
SELECT
|
33
|
+
schemaname AS schema,
|
34
|
+
relname AS table,
|
35
|
+
n_live_tup rows_in_table
|
36
|
+
FROM
|
37
|
+
pg_stat_user_tables
|
38
|
+
WHERE
|
39
|
+
idx_scan = 0
|
40
|
+
ORDER BY
|
41
|
+
n_live_tup DESC,
|
42
|
+
relname ASC
|
43
|
+
SQL
|
44
|
+
end
|
45
|
+
|
46
|
+
def table_stats(options = {})
|
47
|
+
schema = options[:schema]
|
48
|
+
tables = options[:table] ? Array(options[:table]) : nil
|
49
|
+
select_all <<-SQL
|
50
|
+
SELECT
|
51
|
+
nspname AS schema,
|
52
|
+
relname AS table,
|
53
|
+
reltuples::bigint
|
54
|
+
FROM
|
55
|
+
pg_class
|
56
|
+
INNER JOIN
|
57
|
+
pg_namespace ON pg_namespace.oid = pg_class.relnamespace
|
58
|
+
WHERE
|
59
|
+
relkind = 'r'
|
60
|
+
AND nspname = #{quote(schema)}
|
61
|
+
#{tables ? "AND relname IN (#{tables.map { |t| quote(t) }.join(", ")})" : nil}
|
62
|
+
ORDER BY
|
63
|
+
1, 2
|
64
|
+
SQL
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module PgHero
|
2
|
+
module Methods
|
3
|
+
module Users
|
4
|
+
def create_user(user, options = {})
|
5
|
+
password = options[:password] || random_password
|
6
|
+
schema = options[:schema] || "public"
|
7
|
+
database = options[:database] || connection_model.connection_config[:database]
|
8
|
+
|
9
|
+
commands =
|
10
|
+
[
|
11
|
+
"CREATE ROLE #{user} LOGIN PASSWORD #{quote(password)}",
|
12
|
+
"GRANT CONNECT ON DATABASE #{database} TO #{user}",
|
13
|
+
"GRANT USAGE ON SCHEMA #{schema} TO #{user}"
|
14
|
+
]
|
15
|
+
if options[:readonly]
|
16
|
+
if options[:tables]
|
17
|
+
commands.concat table_grant_commands("SELECT", options[:tables], user)
|
18
|
+
else
|
19
|
+
commands << "GRANT SELECT ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
|
20
|
+
commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT SELECT ON TABLES TO #{user}"
|
21
|
+
end
|
22
|
+
else
|
23
|
+
if options[:tables]
|
24
|
+
commands.concat table_grant_commands("ALL PRIVILEGES", options[:tables], user)
|
25
|
+
else
|
26
|
+
commands << "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
|
27
|
+
commands << "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA #{schema} TO #{user}"
|
28
|
+
commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT ALL PRIVILEGES ON TABLES TO #{user}"
|
29
|
+
commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT ALL PRIVILEGES ON SEQUENCES TO #{user}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# run commands
|
34
|
+
connection_model.transaction do
|
35
|
+
commands.each do |command|
|
36
|
+
execute command
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
{password: password}
|
41
|
+
end
|
42
|
+
|
43
|
+
def drop_user(user, options = {})
|
44
|
+
schema = options[:schema] || "public"
|
45
|
+
database = options[:database] || connection_model.connection_config[:database]
|
46
|
+
|
47
|
+
# thanks shiftb
|
48
|
+
commands =
|
49
|
+
[
|
50
|
+
"REVOKE CONNECT ON DATABASE #{database} FROM #{user}",
|
51
|
+
"REVOKE USAGE ON SCHEMA #{schema} FROM #{user}",
|
52
|
+
"REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA #{schema} FROM #{user}",
|
53
|
+
"REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA #{schema} FROM #{user}",
|
54
|
+
"ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} REVOKE SELECT ON TABLES FROM #{user}",
|
55
|
+
"ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} REVOKE SELECT ON SEQUENCES FROM #{user}",
|
56
|
+
"ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} REVOKE ALL ON SEQUENCES FROM #{user}",
|
57
|
+
"ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} REVOKE ALL ON TABLES FROM #{user}",
|
58
|
+
"DROP ROLE #{user}"
|
59
|
+
]
|
60
|
+
|
61
|
+
# run commands
|
62
|
+
connection_model.transaction do
|
63
|
+
commands.each do |command|
|
64
|
+
execute command
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def random_password
|
74
|
+
require "securerandom"
|
75
|
+
SecureRandom.base64(40).delete("+/=")[0...24]
|
76
|
+
end
|
77
|
+
|
78
|
+
def table_grant_commands(privilege, tables, user)
|
79
|
+
tables.map do |table|
|
80
|
+
"GRANT #{privilege} ON TABLE #{table} TO #{user}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/pghero/version.rb
CHANGED
@@ -6,9 +6,10 @@ class SuggestedIndexesTest < Minitest::Test
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def test_basic
|
9
|
+
# no pg_stat_statements
|
10
|
+
skip if ENV["TRAVIS_CI"]
|
11
|
+
|
9
12
|
User.where(email: "person1@example.org").first
|
10
|
-
# User.where(email: "person1@example.org", city_id: 1).first
|
11
|
-
# User.where(city_id: 1).to_a
|
12
13
|
assert_equal [{table: "users", columns: ["email"]}], PgHero.suggested_indexes.map { |q| q.except(:queries, :details) }
|
13
14
|
end
|
14
15
|
end
|
data/test/test_helper.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: 1.2.
|
4
|
+
version: 1.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -116,6 +116,7 @@ extensions: []
|
|
116
116
|
extra_rdoc_files: []
|
117
117
|
files:
|
118
118
|
- ".gitignore"
|
119
|
+
- ".travis.yml"
|
119
120
|
- CHANGELOG.md
|
120
121
|
- Gemfile
|
121
122
|
- LICENSE.txt
|
@@ -148,16 +149,30 @@ files:
|
|
148
149
|
- lib/generators/pghero/query_stats_generator.rb
|
149
150
|
- lib/generators/pghero/templates/query_stats.rb
|
150
151
|
- lib/pghero.rb
|
152
|
+
- lib/pghero/connection.rb
|
151
153
|
- lib/pghero/database.rb
|
152
154
|
- lib/pghero/engine.rb
|
153
|
-
- lib/pghero/
|
155
|
+
- lib/pghero/methods/basic.rb
|
156
|
+
- lib/pghero/methods/connections.rb
|
157
|
+
- lib/pghero/methods/databases.rb
|
158
|
+
- lib/pghero/methods/explain.rb
|
159
|
+
- lib/pghero/methods/indexes.rb
|
160
|
+
- lib/pghero/methods/kill.rb
|
161
|
+
- lib/pghero/methods/maintenance.rb
|
162
|
+
- lib/pghero/methods/queries.rb
|
163
|
+
- lib/pghero/methods/query_stats.rb
|
164
|
+
- lib/pghero/methods/replica.rb
|
165
|
+
- lib/pghero/methods/space.rb
|
166
|
+
- lib/pghero/methods/suggested_indexes.rb
|
167
|
+
- lib/pghero/methods/system.rb
|
168
|
+
- lib/pghero/methods/tables.rb
|
169
|
+
- lib/pghero/methods/users.rb
|
170
|
+
- lib/pghero/query_stats.rb
|
154
171
|
- lib/pghero/version.rb
|
172
|
+
- lib/tasks/pghero.rake
|
155
173
|
- pghero.gemspec
|
156
174
|
- test/best_index_test.rb
|
157
175
|
- test/explain_test.rb
|
158
|
-
- test/gemfiles/activerecord31.gemfile
|
159
|
-
- test/gemfiles/activerecord32.gemfile
|
160
|
-
- test/gemfiles/activerecord40.gemfile
|
161
176
|
- test/suggested_indexes_test.rb
|
162
177
|
- test/test_helper.rb
|
163
178
|
homepage: https://github.com/ankane/pghero
|
@@ -180,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
195
|
version: '0'
|
181
196
|
requirements: []
|
182
197
|
rubyforge_project:
|
183
|
-
rubygems_version: 2.
|
198
|
+
rubygems_version: 2.6.1
|
184
199
|
signing_key:
|
185
200
|
specification_version: 4
|
186
201
|
summary: The missing dashboard for Postgres
|
@@ -193,9 +208,6 @@ test_files:
|
|
193
208
|
- guides/Suggested-Indexes.md
|
194
209
|
- test/best_index_test.rb
|
195
210
|
- test/explain_test.rb
|
196
|
-
- test/gemfiles/activerecord31.gemfile
|
197
|
-
- test/gemfiles/activerecord32.gemfile
|
198
|
-
- test/gemfiles/activerecord40.gemfile
|
199
211
|
- test/suggested_indexes_test.rb
|
200
212
|
- test/test_helper.rb
|
201
213
|
has_rdoc:
|