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.

Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -0
  3. data/CHANGELOG.md +6 -0
  4. data/README.md +1 -1
  5. data/app/controllers/pg_hero/home_controller.rb +15 -4
  6. data/app/views/layouts/pg_hero/application.html.erb +4 -4
  7. data/app/views/pg_hero/home/explain.html.erb +1 -1
  8. data/app/views/pg_hero/home/index_usage.html.erb +6 -1
  9. data/app/views/pg_hero/home/maintenance.html.erb +6 -1
  10. data/app/views/pg_hero/home/space.html.erb +5 -3
  11. data/lib/pghero.rb +35 -1243
  12. data/lib/pghero/connection.rb +5 -0
  13. data/lib/pghero/database.rb +12 -3
  14. data/lib/pghero/methods/basic.rb +104 -0
  15. data/lib/pghero/methods/connections.rb +49 -0
  16. data/lib/pghero/methods/databases.rb +39 -0
  17. data/lib/pghero/methods/explain.rb +29 -0
  18. data/lib/pghero/methods/indexes.rb +154 -0
  19. data/lib/pghero/methods/kill.rb +27 -0
  20. data/lib/pghero/methods/maintenance.rb +61 -0
  21. data/lib/pghero/methods/queries.rb +73 -0
  22. data/lib/pghero/methods/query_stats.rb +188 -0
  23. data/lib/pghero/methods/replica.rb +22 -0
  24. data/lib/pghero/methods/space.rb +30 -0
  25. data/lib/pghero/methods/suggested_indexes.rb +322 -0
  26. data/lib/pghero/methods/system.rb +70 -0
  27. data/lib/pghero/methods/tables.rb +68 -0
  28. data/lib/pghero/methods/users.rb +85 -0
  29. data/lib/pghero/query_stats.rb +7 -0
  30. data/lib/pghero/version.rb +1 -1
  31. data/lib/{pghero/tasks.rb → tasks/pghero.rake} +0 -2
  32. data/test/suggested_indexes_test.rb +3 -2
  33. data/test/test_helper.rb +1 -1
  34. metadata +22 -10
  35. data/test/gemfiles/activerecord31.gemfile +0 -6
  36. data/test/gemfiles/activerecord32.gemfile +0 -6
  37. 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
@@ -0,0 +1,7 @@
1
+ module PgHero
2
+ class QueryStats < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ self.table_name = "pghero_query_stats"
5
+ establish_connection ENV["PGHERO_STATS_DATABASE_URL"] if ENV["PGHERO_STATS_DATABASE_URL"]
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module PgHero
2
- VERSION = "1.2.2"
2
+ VERSION = "1.2.3"
3
3
  end
@@ -1,5 +1,3 @@
1
- require "rake"
2
-
3
1
  namespace :pghero do
4
2
  desc "capture query stats"
5
3
  task capture_query_stats: :environment do
@@ -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
@@ -23,7 +23,7 @@ end
23
23
  class User < ActiveRecord::Base
24
24
  end
25
25
 
26
- if ENV["SEED"]
26
+ unless ENV["SKIP_SEED"]
27
27
  ActiveRecord::Migration.create_table :users, force: true do |t|
28
28
  t.integer :city_id
29
29
  t.integer :login_attempts
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.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-01-21 00:00:00.000000000 Z
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/tasks.rb
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.4.5.1
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:
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in searchkick.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "activerecord", "~> 3.1.0"
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in searchkick.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "activerecord", "~> 3.2.0"
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in searchkick.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "activerecord", "~> 4.0.0"