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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +8 -8
- data/app/assets/javascripts/pghero/Chart.bundle.js +183 -55
- data/app/assets/javascripts/pghero/chartkick.js +53 -20
- data/app/assets/stylesheets/pghero/application.css +7 -0
- data/app/controllers/pg_hero/home_controller.rb +61 -57
- data/app/views/layouts/pg_hero/application.html.erb +3 -3
- data/app/views/pg_hero/home/_connections_table.html.erb +1 -1
- data/app/views/pg_hero/home/_queries_table.html.erb +6 -1
- data/app/views/pg_hero/home/connections.html.erb +1 -1
- data/app/views/pg_hero/home/explain.html.erb +11 -2
- data/app/views/pg_hero/home/index.html.erb +4 -4
- data/app/views/pg_hero/home/maintenance.html.erb +2 -2
- data/app/views/pg_hero/home/system.html.erb +2 -2
- data/guides/Rails.md +8 -0
- data/lib/generators/pghero/space_stats_generator.rb +29 -0
- data/lib/generators/pghero/templates/space_stats.rb +13 -0
- data/lib/pghero.rb +109 -23
- data/lib/pghero/database.rb +46 -8
- data/lib/pghero/engine.rb +3 -1
- data/lib/pghero/methods/basic.rb +3 -42
- data/lib/pghero/methods/connections.rb +18 -1
- data/lib/pghero/methods/explain.rb +2 -0
- data/lib/pghero/methods/indexes.rb +2 -2
- data/lib/pghero/methods/kill.rb +1 -1
- data/lib/pghero/methods/queries.rb +2 -2
- data/lib/pghero/methods/query_stats.rb +81 -75
- data/lib/pghero/methods/space.rb +12 -1
- data/lib/pghero/methods/suggested_indexes.rb +71 -31
- data/lib/pghero/version.rb +1 -1
- data/lib/tasks/pghero.rake +5 -0
- metadata +4 -3
- 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:
|
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
|
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
|
-
|
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 <%=
|
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
|
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:
|
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 <%=
|
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
|
-
<%=
|
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
|
-
<%=
|
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
|
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
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
data/lib/pghero/database.rb
CHANGED
@@ -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 "
|
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
|
data/lib/pghero/methods/basic.rb
CHANGED
@@ -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
|
-
|
55
|
-
|
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
|