pghero 1.4.2 → 1.5.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.

Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +8 -8
  4. data/app/assets/javascripts/pghero/Chart.bundle.js +183 -55
  5. data/app/assets/javascripts/pghero/chartkick.js +53 -20
  6. data/app/assets/stylesheets/pghero/application.css +7 -0
  7. data/app/controllers/pg_hero/home_controller.rb +61 -57
  8. data/app/views/layouts/pg_hero/application.html.erb +3 -3
  9. data/app/views/pg_hero/home/_connections_table.html.erb +1 -1
  10. data/app/views/pg_hero/home/_queries_table.html.erb +6 -1
  11. data/app/views/pg_hero/home/connections.html.erb +1 -1
  12. data/app/views/pg_hero/home/explain.html.erb +11 -2
  13. data/app/views/pg_hero/home/index.html.erb +4 -4
  14. data/app/views/pg_hero/home/maintenance.html.erb +2 -2
  15. data/app/views/pg_hero/home/system.html.erb +2 -2
  16. data/guides/Rails.md +8 -0
  17. data/lib/generators/pghero/space_stats_generator.rb +29 -0
  18. data/lib/generators/pghero/templates/space_stats.rb +13 -0
  19. data/lib/pghero.rb +109 -23
  20. data/lib/pghero/database.rb +46 -8
  21. data/lib/pghero/engine.rb +3 -1
  22. data/lib/pghero/methods/basic.rb +3 -42
  23. data/lib/pghero/methods/connections.rb +18 -1
  24. data/lib/pghero/methods/explain.rb +2 -0
  25. data/lib/pghero/methods/indexes.rb +2 -2
  26. data/lib/pghero/methods/kill.rb +1 -1
  27. data/lib/pghero/methods/queries.rb +2 -2
  28. data/lib/pghero/methods/query_stats.rb +81 -75
  29. data/lib/pghero/methods/space.rb +12 -1
  30. data/lib/pghero/methods/suggested_indexes.rb +71 -31
  31. data/lib/pghero/version.rb +1 -1
  32. data/lib/tasks/pghero.rake +5 -0
  33. metadata +4 -3
  34. data/lib/pghero/methods/databases.rb +0 -39
@@ -1,5 +1,5 @@
1
1
  <div class="content">
2
2
  <h1>Connections</h1>
3
3
 
4
- <%= render partial: "connections_table", locals: {total_connections: @total_connections, connection_sources: PgHero.connection_sources(by_database: true), show_message: false} %>
4
+ <%= render partial: "connections_table", locals: {total_connections: @total_connections, connection_sources: @connection_sources, show_message: false} %>
5
5
  </div>
@@ -3,12 +3,21 @@
3
3
 
4
4
  <%= form_tag explain_path do %>
5
5
  <%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %>
6
- <p><%= submit_tag "Explain", class: "btn btn-info" %> <%= submit_tag "Analyze", class: "btn btn-danger" %></p>
6
+ <p>
7
+ <%= submit_tag "Explain", class: "btn btn-info", style: "margin-right: 10px;" %>
8
+ <%= submit_tag "Analyze", class: "btn btn-danger", style: "margin-right: 10px;" %>
9
+ <%= submit_tag "Visualize", class: "btn btn-danger" %>
10
+ </p>
7
11
  <% end %>
8
12
 
9
13
  <% if @explanation %>
14
+ <% if @visualize %>
15
+ <p>Paste the output below into the <%= link_to "Postgres Explain Visualizer", "http://tatiyants.com/pev/#/plans/new", target: "_blank" %></p>
16
+ <% end %>
10
17
  <pre><%= @explanation %></pre>
11
- <p><%= link_to "See how to interpret this", "http://www.postgresql.org/docs/current/static/using-explain.html", target: "_blank" %></p>
18
+ <% unless @visualize %>
19
+ <p><%= link_to "See how to interpret this", "http://www.postgresql.org/docs/current/static/using-explain.html", target: "_blank" %></p>
20
+ <% end %>
12
21
  <% if (index = @suggested_index) %>
13
22
  <%= render partial: "suggested_index", locals: {index: index, details: index[:details]} %>
14
23
  <% end %>
@@ -18,7 +18,7 @@
18
18
  </div>
19
19
  <div class="alert alert-<%= @good_cache_rate ? "success" : "warning" %>">
20
20
  <% if @good_cache_rate %>
21
- Cache hit rate above <%= PgHero.cache_hit_rate_threshold %>%
21
+ Cache hit rate above <%= @database.cache_hit_rate_threshold %>%
22
22
  <% else %>
23
23
  Low cache hit rate
24
24
  <% end %>
@@ -61,7 +61,7 @@
61
61
  <% end %>
62
62
  </div>
63
63
  <% end %>
64
- <% if PgHero.suggested_indexes_enabled? %>
64
+ <% if @database.suggested_indexes_enabled? %>
65
65
  <div class="alert alert-<%= @suggested_indexes.empty? ? "success" : "warning" %>">
66
66
  <% if @suggested_indexes.any? %>
67
67
  <%= pluralize(@suggested_indexes.size, "suggested index", "suggested indexes") %>
@@ -126,7 +126,7 @@
126
126
  <% if !@good_total_connections %>
127
127
  <div class="content">
128
128
  <h1>High Number of Connections</h1>
129
- <%= render partial: "connections_table", locals: {total_connections: @total_connections, connection_sources: PgHero.connection_sources(by_database: true).first(10), show_message: true} %>
129
+ <%= render partial: "connections_table", locals: {total_connections: @total_connections, connection_sources: @database.connection_sources(by_database_and_user: true).first(10), show_message: true} %>
130
130
  </div>
131
131
  <% end %>
132
132
 
@@ -335,7 +335,7 @@ pg_stat_statements.track = all</pre>
335
335
  <div class="content">
336
336
  <h1>Slow Queries</h1>
337
337
 
338
- <p>Slow queries take <%= PgHero.slow_query_ms %> ms or more on average and have been called at least <%= PgHero.slow_query_calls %> times.</p>
338
+ <p>Slow queries take <%= @database.slow_query_ms %> ms or more on average and have been called at least <%= @database.slow_query_calls %> times.</p>
339
339
  <p><%= link_to "Explain queries", explain_path %> to see where to add indexes.</p>
340
340
 
341
341
  <%= render partial: "queries_table", locals: {queries: @slow_queries} %>
@@ -21,13 +21,13 @@
21
21
  <td>
22
22
  <% time = [table["last_autovacuum"], table["last_vacuum"]].compact.max %>
23
23
  <% if time %>
24
- <%= PgHero.time_zone.parse(time).strftime("%m/%-e %l:%M %P") %>
24
+ <%= @time_zone.parse(time).strftime("%m/%-e %l:%M %P") %>
25
25
  <% end %>
26
26
  </td>
27
27
  <td>
28
28
  <% time = [table["last_autoanalyze"], table["last_analyze"]].compact.max %>
29
29
  <% if time %>
30
- <%= PgHero.time_zone.parse(time).strftime("%m/%-e %l:%M %P") %>
30
+ <%= @time_zone.parse(time).strftime("%m/%-e %l:%M %P") %>
31
31
  <% end %>
32
32
  </td>
33
33
  </tr>
@@ -4,7 +4,7 @@
4
4
  <%= link_to name, system_path(options) %>
5
5
  <% end %>
6
6
  </p>
7
- <% path_options = params.slice(:duration, :period) %>
7
+ <% path_options = {duration: params[:duration], period: params[:period]} %>
8
8
 
9
9
  <h1>CPU</h1>
10
10
  <div style="margin-bottom: 20px;"><%= line_chart cpu_usage_path(path_options), max: 100, colors: ["#5bc0de"] %></div>
@@ -15,7 +15,7 @@
15
15
  <h1>Connections</h1>
16
16
  <div style="margin-bottom: 20px;"><%= line_chart connection_stats_path(path_options), colors: ["#5bc0de"] %></div>
17
17
 
18
- <% if PgHero.replica? %>
18
+ <% if @database.replica? %>
19
19
  <h1>Replication Lag</h1>
20
20
  <div style="margin-bottom: 20px;"><%= line_chart replication_lag_stats_path(path_options), colors: ["#5bc0de"] %></div>
21
21
  <% end %>
data/guides/Rails.md CHANGED
@@ -244,6 +244,14 @@ PgHero.total_connections_threshold = 100 # default
244
244
 
245
245
  ## Upgrading
246
246
 
247
+ ### 1.5.0
248
+
249
+ For query stats grouping by user, create a migration with:
250
+
251
+ ```ruby
252
+ add_column :pghero_query_stats, :user, :text
253
+ ```
254
+
247
255
  ### 1.3.0
248
256
 
249
257
  For better query stats grouping with Postgres 9.4+, create a migration with:
@@ -0,0 +1,29 @@
1
+ # taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
2
+ require "rails/generators"
3
+ require "rails/generators/migration"
4
+ require "active_record"
5
+ require "rails/generators/active_record"
6
+
7
+ module Pghero
8
+ module Generators
9
+ class SpaceStatsGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+
12
+ source_root File.expand_path("../templates", __FILE__)
13
+
14
+ # Implement the required interface for Rails::Generators::Migration.
15
+ def self.next_migration_number(dirname) #:nodoc:
16
+ next_migration_number = current_migration_number(dirname) + 1
17
+ if ::ActiveRecord::Base.timestamped_migrations
18
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
19
+ else
20
+ "%.3d" % next_migration_number
21
+ end
22
+ end
23
+
24
+ def copy_migration
25
+ migration_template "space_stats.rb", "db/migrate/create_pghero_space_stats.rb"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ create_table :pghero_space_stats do |t|
4
+ t.text :database
5
+ t.text :schema
6
+ t.text :relation
7
+ t.integer :size, limit: 8
8
+ t.timestamp :captured_at
9
+ end
10
+
11
+ add_index :pghero_space_stats, [:database, :captured_at]
12
+ end
13
+ end
data/lib/pghero.rb CHANGED
@@ -1,16 +1,9 @@
1
1
  require "pghero/version"
2
2
  require "active_record"
3
- require "pghero/database"
4
- require "pghero/engine" if defined?(Rails)
5
-
6
- # models
7
- require "pghero/connection"
8
- require "pghero/query_stats"
9
3
 
10
4
  # methods
11
5
  require "pghero/methods/basic"
12
6
  require "pghero/methods/connections"
13
- require "pghero/methods/databases"
14
7
  require "pghero/methods/explain"
15
8
  require "pghero/methods/indexes"
16
9
  require "pghero/methods/kill"
@@ -25,6 +18,13 @@ require "pghero/methods/system"
25
18
  require "pghero/methods/tables"
26
19
  require "pghero/methods/users"
27
20
 
21
+ require "pghero/database"
22
+ require "pghero/engine" if defined?(Rails)
23
+
24
+ # models
25
+ require "pghero/connection"
26
+ require "pghero/query_stats"
27
+
28
28
  module PgHero
29
29
  # settings
30
30
  class << self
@@ -38,20 +38,106 @@ module PgHero
38
38
  self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
39
39
  self.show_migrations = true
40
40
 
41
- extend Methods::Basic
42
- extend Methods::Connections
43
- extend Methods::Databases
44
- extend Methods::Explain
45
- extend Methods::Indexes
46
- extend Methods::Kill
47
- extend Methods::Maintenance
48
- extend Methods::Queries
49
- extend Methods::QueryStats
50
- extend Methods::Replica
51
- extend Methods::Sequences
52
- extend Methods::Space
53
- extend Methods::SuggestedIndexes
54
- extend Methods::System
55
- extend Methods::Tables
56
- extend Methods::Users
41
+ class << self
42
+ extend Forwardable
43
+ def_delegators :current_database, :access_key_id, :autoindex, :autoindex_all, :autovacuum_danger,
44
+ :best_index, :blocked_queries, :capture_query_stats, :connection_sources, :connection_stats,
45
+ :cpu_usage, :create_user, :database_size, :db_instance_identifier, :disable_query_stats, :drop_user,
46
+ :duplicate_indexes, :enable_query_stats, :explain, :historical_query_stats_enabled?, :index_caching,
47
+ :index_hit_rate, :index_usage, :indexes, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
48
+ :locks, :long_running_queries, :maintenance_info, :missing_indexes, :query_stats,
49
+ :query_stats_available?, :query_stats_enabled?, :query_stats_extension_enabled?, :query_stats_readable?,
50
+ :rds_stats, :read_iops_stats, :region, :relation_sizes, :replica?, :replication_lag, :replication_lag_stats,
51
+ :reset_query_stats, :running_queries, :secret_access_key, :sequence_danger, :sequences, :settings,
52
+ :slow_queries, :ssl_used?, :stats_connection, :suggested_indexes, :suggested_indexes_by_query,
53
+ :suggested_indexes_enabled?, :system_stats_enabled?, :table_caching, :table_hit_rate, :table_stats,
54
+ :total_connections, :transaction_id_danger, :unused_indexes, :unused_tables, :write_iops_stats
55
+
56
+ def time_zone=(time_zone)
57
+ @time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
58
+ end
59
+
60
+ def time_zone
61
+ @time_zone || Time.zone
62
+ end
63
+
64
+ def config
65
+ Thread.current[:pghero_config] ||= begin
66
+ path = "config/pghero.yml"
67
+
68
+ config =
69
+ (YAML.load(ERB.new(File.read(path)).result)[env] if File.exist?(path))
70
+
71
+ if config
72
+ config
73
+ else
74
+ {
75
+ "databases" => {
76
+ "primary" => {
77
+ "url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config,
78
+ "db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"]
79
+ }
80
+ }
81
+ }
82
+ end
83
+ end
84
+ end
85
+
86
+ def databases
87
+ @databases ||= begin
88
+ Hash[
89
+ config["databases"].map do |id, c|
90
+ [id, PgHero::Database.new(id, c)]
91
+ end
92
+ ]
93
+ end
94
+ end
95
+
96
+ def primary_database
97
+ databases.values.first
98
+ end
99
+
100
+ def current_database
101
+ Thread.current[:pghero_current_database] ||= primary_database
102
+ end
103
+
104
+ def current_database=(database)
105
+ raise "Database not found" unless databases[database.to_s]
106
+ Thread.current[:pghero_current_database] = databases[database.to_s]
107
+ database
108
+ end
109
+
110
+ def with(database)
111
+ previous_database = current_database
112
+ begin
113
+ self.current_database = database
114
+ yield
115
+ ensure
116
+ self.current_database = previous_database.id
117
+ end
118
+ end
119
+
120
+ def capture_query_stats
121
+ databases.each do |_, database|
122
+ database.capture_query_stats
123
+ end
124
+ true
125
+ end
126
+
127
+ def capture_space_stats
128
+ databases.each do |_, database|
129
+ database.capture_space_stats
130
+ end
131
+ true
132
+ end
133
+
134
+ # Handles Rails 4 ('t') and Rails 5 (true) values.
135
+ def truthy?(value)
136
+ value == true || value == 't'
137
+ end
138
+
139
+ def falsey?(value)
140
+ value == false || value == 'f'
141
+ end
142
+ end
57
143
  end
@@ -1,5 +1,21 @@
1
1
  module PgHero
2
2
  class Database
3
+ include Methods::Basic
4
+ include Methods::Connections
5
+ include Methods::Explain
6
+ include Methods::Indexes
7
+ include Methods::Kill
8
+ include Methods::Maintenance
9
+ include Methods::Queries
10
+ include Methods::QueryStats
11
+ include Methods::Replica
12
+ include Methods::Sequences
13
+ include Methods::Space
14
+ include Methods::SuggestedIndexes
15
+ include Methods::System
16
+ include Methods::Tables
17
+ include Methods::Users
18
+
3
19
  attr_reader :id, :config
4
20
 
5
21
  def initialize(id, config)
@@ -7,6 +23,36 @@ module PgHero
7
23
  @config = config
8
24
  end
9
25
 
26
+ def name
27
+ @name ||= @config["name"] || id.titleize
28
+ end
29
+
30
+ def db_instance_identifier
31
+ @db_instance_identifier ||= @config["db_instance_identifier"]
32
+ end
33
+
34
+ def capture_query_stats?
35
+ config["capture_query_stats"] != false
36
+ end
37
+
38
+ def cache_hit_rate_threshold
39
+ (config["cache_hit_rate_threshold"] || PgHero.config["cache_hit_rate_threshold"] || PgHero.cache_hit_rate_threshold).to_i
40
+ end
41
+
42
+ def total_connections_threshold
43
+ (config["total_connections_threshold"] || PgHero.config["total_connections_threshold"] || PgHero.total_connections_threshold).to_i
44
+ end
45
+
46
+ def slow_query_ms
47
+ (config["slow_query_ms"] || PgHero.config["slow_query_ms"] || PgHero.slow_query_ms).to_i
48
+ end
49
+
50
+ def slow_query_calls
51
+ (config["slow_query_calls"] || PgHero.config["slow_query_calls"] || PgHero.slow_query_calls).to_i
52
+ end
53
+
54
+ private
55
+
10
56
  def connection_model
11
57
  @connection_model ||= begin
12
58
  url = config["url"]
@@ -18,13 +64,5 @@ module PgHero
18
64
  end
19
65
  end
20
66
  end
21
-
22
- def db_instance_identifier
23
- @db_instance_identifier ||= @config["db_instance_identifier"]
24
- end
25
-
26
- def name
27
- @name ||= @config["name"] || id.titleize
28
- end
29
67
  end
30
68
  end
data/lib/pghero/engine.rb CHANGED
@@ -2,7 +2,7 @@ module PgHero
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace PgHero
4
4
 
5
- initializer "precompile", group: :all do |app|
5
+ initializer "pghero", group: :all do |app|
6
6
  if defined?(Sprockets) && Sprockets::VERSION >= "4"
7
7
  app.config.assets.precompile << "pghero/application.js"
8
8
  app.config.assets.precompile << "pghero/application.css"
@@ -11,6 +11,8 @@ module PgHero
11
11
  app.config.assets.precompile << proc { |path| path == "pghero/application.js" }
12
12
  app.config.assets.precompile << proc { |path| path == "pghero/application.css" }
13
13
  end
14
+
15
+ PgHero.time_zone = PgHero.config["time_zone"] if PgHero.config["time_zone"]
14
16
  end
15
17
  end
16
18
  end
@@ -1,36 +1,6 @@
1
1
  module PgHero
2
2
  module Methods
3
3
  module Basic
4
- def time_zone=(time_zone)
5
- @time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
6
- end
7
-
8
- def time_zone
9
- @time_zone || Time.zone
10
- end
11
-
12
- def config
13
- Thread.current[:pghero_config] ||= begin
14
- path = "config/pghero.yml"
15
-
16
- config =
17
- (YAML.load(ERB.new(File.read(path)).result)[env] if File.exist?(path))
18
-
19
- if config
20
- config
21
- else
22
- {
23
- "databases" => {
24
- "primary" => {
25
- "url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config,
26
- "db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"]
27
- }
28
- }
29
- }
30
- end
31
- end
32
- end
33
-
34
4
  def settings
35
5
  names = %w(
36
6
  max_connections shared_buffers effective_cache_size work_mem
@@ -45,19 +15,14 @@ module PgHero
45
15
  ssl_used = nil
46
16
  connection_model.transaction do
47
17
  execute("CREATE EXTENSION IF NOT EXISTS sslinfo")
48
- ssl_used = truthy?(select_all("SELECT ssl_is_used()").first["ssl_is_used"])
18
+ ssl_used = PgHero.truthy?(select_all("SELECT ssl_is_used()").first["ssl_is_used"])
49
19
  raise ActiveRecord::Rollback
50
20
  end
51
21
  ssl_used
52
22
  end
53
23
 
54
- # Handles Rails 4 ('t') and Rails 5 (true) values.
55
- def truthy?(value)
56
- value == true || value == 't'
57
- end
58
-
59
- def falsey?(value)
60
- value == false || value == 'f'
24
+ def database_name
25
+ select_all("SELECT current_database()").first["current_database"]
61
26
  end
62
27
 
63
28
  private
@@ -88,10 +53,6 @@ module PgHero
88
53
  connection.execute(sql)
89
54
  end
90
55
 
91
- def connection_model
92
- databases[current_database].connection_model
93
- end
94
-
95
56
  def connection
96
57
  connection_model.connection
97
58
  end