pghero 0.1.9 → 0.1.10
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 +6 -0
- data/Gemfile +1 -1
- data/Rakefile +1 -1
- data/app/controllers/pg_hero/home_controller.rb +15 -15
- data/app/views/layouts/pg_hero/application.html.erb +1 -0
- data/app/views/pg_hero/home/_connections_table.html.erb +23 -0
- data/app/views/pg_hero/home/_query_stats_table.html.erb +6 -1
- data/app/views/pg_hero/home/connections.html.erb +5 -0
- data/app/views/pg_hero/home/index.html.erb +1 -21
- data/config/routes.rb +1 -0
- data/lib/pghero.rb +21 -25
- data/lib/pghero/version.rb +1 -1
- data/pghero.gemspec +4 -4
- data/test/pghero_test.rb +1 -3
- data/test/test_helper.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b36df22eb125725ef48c4c9c1cb4094a2cd346b8
|
4
|
+
data.tar.gz: d683feef9adc1a6b0b09ed8ea844159e24b250cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42c4d3ccc26df2ac3a0011051acad9f9b06e8f61da89eed183a275d5d3a1752bbcb5a7a756ea26f3bfb20e6e05832c310c1bc87b63cfc6be194eb2b2ce46f247
|
7
|
+
data.tar.gz: 1e584dd852a119509488d59f6fb5346d211e43da9cf221000f96899cb71ea9bf8002780b00d1e70586664c435bfea764a09816cfd992ac642ae350fe4a1ccea3
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -45,7 +45,7 @@ module PgHero
|
|
45
45
|
|
46
46
|
def system_stats
|
47
47
|
@title = "System Stats"
|
48
|
-
@cpu_usage = PgHero.cpu_usage.map{|k, v| [k, v.round] }
|
48
|
+
@cpu_usage = PgHero.cpu_usage.map { |k, v| [k, v.round] }
|
49
49
|
@connection_stats = PgHero.connection_stats
|
50
50
|
end
|
51
51
|
|
@@ -54,7 +54,7 @@ module PgHero
|
|
54
54
|
@query = params[:query]
|
55
55
|
# TODO use get + token instead of post so users can share links
|
56
56
|
# need to prevent CSRF and DoS
|
57
|
-
if request.post?
|
57
|
+
if request.post? && @query
|
58
58
|
begin
|
59
59
|
@explanation = PgHero.explain(@query)
|
60
60
|
rescue ActiveRecord::StatementInvalid => e
|
@@ -68,6 +68,11 @@ module PgHero
|
|
68
68
|
@settings = PgHero.settings
|
69
69
|
end
|
70
70
|
|
71
|
+
def connections
|
72
|
+
@title = "Connections"
|
73
|
+
@total_connections = PgHero.total_connections
|
74
|
+
end
|
75
|
+
|
71
76
|
def kill
|
72
77
|
if PgHero.kill(params[:pid])
|
73
78
|
redirect_to root_path, notice: "Query killed"
|
@@ -82,21 +87,17 @@ module PgHero
|
|
82
87
|
end
|
83
88
|
|
84
89
|
def enable_query_stats
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
redirect_to :back, alert: "The database user does not have permission to enable query stats"
|
90
|
-
end
|
90
|
+
PgHero.enable_query_stats
|
91
|
+
redirect_to :back, notice: "Query stats enabled"
|
92
|
+
rescue ActiveRecord::StatementInvalid
|
93
|
+
redirect_to :back, alert: "The database user does not have permission to enable query stats"
|
91
94
|
end
|
92
95
|
|
93
96
|
def reset_query_stats
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
redirect_to :back, alert: "The database user does not have permission to reset query stats"
|
99
|
-
end
|
97
|
+
PgHero.reset_query_stats
|
98
|
+
redirect_to :back, notice: "Query stats reset"
|
99
|
+
rescue ActiveRecord::StatementInvalid
|
100
|
+
redirect_to :back, alert: "The database user does not have permission to reset query stats"
|
100
101
|
end
|
101
102
|
|
102
103
|
protected
|
@@ -105,6 +106,5 @@ module PgHero
|
|
105
106
|
@query_stats_enabled = PgHero.query_stats_enabled?
|
106
107
|
@system_stats_enabled = PgHero.system_stats_enabled?
|
107
108
|
end
|
108
|
-
|
109
109
|
end
|
110
110
|
end
|
@@ -362,6 +362,7 @@
|
|
362
362
|
<li class="<%= controller.action_name == "space" ? "active" : "" %>"><%= link_to "Space", space_path %></li>
|
363
363
|
<li class="<%= controller.action_name == "explain" ? "active" : "" %>"><%= link_to "Explain", explain_path %></li>
|
364
364
|
<li class="<%= controller.action_name == "queries" ? "active" : "" %>"><%= link_to "Live Queries", queries_path %></li>
|
365
|
+
<li class="<%= controller.action_name == "connections" ? "active" : "" %>"><%= link_to "Connections", connections_path %></li>
|
365
366
|
<li class="<%= controller.action_name == "tune" ? "active" : "" %>"><%= link_to "Tune", tune_path %></li>
|
366
367
|
</ul>
|
367
368
|
</div>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<p><%= pluralize(total_connections, "connection") %></p>
|
2
|
+
<% if show_message %>
|
3
|
+
<p>
|
4
|
+
<%= link_to "Use connection pooling", "http://www.craigkerstiens.com/2014/05/22/on-connection-pooling/", target: "_blank" %> for better performance. <%= link_to "PgBouncer", "https://wiki.postgresql.org/wiki/PgBouncer", target: "_blank" %> is a solid option.
|
5
|
+
</p>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<table class="table">
|
9
|
+
<thead>
|
10
|
+
<tr>
|
11
|
+
<th>Top Sources</th>
|
12
|
+
<th>Connections</th>
|
13
|
+
</tr>
|
14
|
+
</thead>
|
15
|
+
<tbody>
|
16
|
+
<% PgHero.connection_sources.first(10).each do |source| %>
|
17
|
+
<tr>
|
18
|
+
<td><%= source["source"] %> <span class="text-muted"><%= source["ip"] %></span></td>
|
19
|
+
<td style="width: 20%;"><%= number_with_delimiter(source["total_connections"]) %></td>
|
20
|
+
</tr>
|
21
|
+
<% end %>
|
22
|
+
</tbody>
|
23
|
+
</table>
|
@@ -26,7 +26,12 @@
|
|
26
26
|
<td><%= number_with_delimiter(query["calls"].to_i) %></td>
|
27
27
|
</tr>
|
28
28
|
<tr>
|
29
|
-
<td colspan="3" style="border-top: none; padding: 0;"
|
29
|
+
<td colspan="3" style="border-top: none; padding: 0;">
|
30
|
+
<pre><%= query["query"] %></pre>
|
31
|
+
<% if query["query"] == "<insufficient privilege>" %>
|
32
|
+
<p class="text-muted">For security reasons, only superusers can see queries executed by other users.</p>
|
33
|
+
<% end %>
|
34
|
+
</td>
|
30
35
|
</tr>
|
31
36
|
<% end %>
|
32
37
|
</tbody>
|
@@ -73,27 +73,7 @@
|
|
73
73
|
<% if !@good_total_connections %>
|
74
74
|
<div class="content">
|
75
75
|
<h1>High Number of Connections</h1>
|
76
|
-
|
77
|
-
<p>
|
78
|
-
<%= link_to "Use connection pooling", "http://www.craigkerstiens.com/2014/05/22/on-connection-pooling/", target: "_blank" %> for better performance. <%= link_to "PgBouncer", "https://wiki.postgresql.org/wiki/PgBouncer", target: "_blank" %> is a solid option.
|
79
|
-
</p>
|
80
|
-
|
81
|
-
<table class="table">
|
82
|
-
<thead>
|
83
|
-
<tr>
|
84
|
-
<th>Top Sources</th>
|
85
|
-
<th>Connections</th>
|
86
|
-
</tr>
|
87
|
-
</thead>
|
88
|
-
<tbody>
|
89
|
-
<% PgHero.connection_sources.first(10).each do |source| %>
|
90
|
-
<tr>
|
91
|
-
<td><%= source["source"] %></td>
|
92
|
-
<td style="width: 20%;"><%= number_with_delimiter(source["total_connections"]) %></td>
|
93
|
-
</tr>
|
94
|
-
<% end %>
|
95
|
-
</tbody>
|
96
|
-
</table>
|
76
|
+
<%= render partial: "connections_table", locals: {total_connections: @total_connections, show_message: true} %>
|
97
77
|
</div>
|
98
78
|
<% end %>
|
99
79
|
|
data/config/routes.rb
CHANGED
data/lib/pghero.rb
CHANGED
@@ -17,7 +17,6 @@ module PgHero
|
|
17
17
|
self.total_connections_threshold = 100
|
18
18
|
|
19
19
|
class << self
|
20
|
-
|
21
20
|
def running_queries
|
22
21
|
select_all <<-SQL
|
23
22
|
SELECT
|
@@ -206,16 +205,19 @@ module PgHero
|
|
206
205
|
select_all <<-SQL
|
207
206
|
SELECT
|
208
207
|
application_name AS source,
|
208
|
+
client_addr AS ip,
|
209
209
|
COUNT(*) AS total_connections
|
210
210
|
FROM
|
211
211
|
pg_stat_activity
|
212
212
|
WHERE
|
213
213
|
pid <> pg_backend_pid()
|
214
214
|
GROUP BY
|
215
|
-
application_name
|
215
|
+
application_name,
|
216
|
+
ip
|
216
217
|
ORDER BY
|
217
218
|
COUNT(*) DESC,
|
218
|
-
application_name ASC
|
219
|
+
application_name ASC,
|
220
|
+
client_addr ASC
|
219
221
|
SQL
|
220
222
|
end
|
221
223
|
|
@@ -315,12 +317,9 @@ module PgHero
|
|
315
317
|
end
|
316
318
|
|
317
319
|
def query_stats_readable?
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
rescue ActiveRecord::StatementInvalid
|
322
|
-
false
|
323
|
-
end
|
320
|
+
select_all("SELECT has_table_privilege(current_user, 'pg_stat_statements', 'SELECT')").first["has_table_privilege"] == "t"
|
321
|
+
rescue ActiveRecord::StatementInvalid
|
322
|
+
false
|
324
323
|
end
|
325
324
|
|
326
325
|
def enable_query_stats
|
@@ -363,7 +362,7 @@ module PgHero
|
|
363
362
|
statistics: ["Average"]
|
364
363
|
)
|
365
364
|
data = {}
|
366
|
-
resp[:datapoints].sort_by{|d| d[:timestamp] }.each do |d|
|
365
|
+
resp[:datapoints].sort_by { |d| d[:timestamp] }.each do |d|
|
367
366
|
data[d[:timestamp]] = d[:average]
|
368
367
|
end
|
369
368
|
data
|
@@ -459,10 +458,10 @@ module PgHero
|
|
459
458
|
|
460
459
|
# use transaction for safety
|
461
460
|
Connection.transaction do
|
462
|
-
if !explain_safe
|
461
|
+
if !explain_safe && (sql.sub(/;\z/, "").include?(";") || sql.upcase.include?("COMMIT"))
|
463
462
|
raise ActiveRecord::StatementInvalid, "Unsafe statement"
|
464
463
|
end
|
465
|
-
explanation = select_all("EXPLAIN #{sql}").map{|v| v["QUERY PLAN"] }.join("\n")
|
464
|
+
explanation = select_all("EXPLAIN #{sql}").map { |v| v["QUERY PLAN"] }.join("\n")
|
466
465
|
raise ActiveRecord::Rollback
|
467
466
|
end
|
468
467
|
|
@@ -470,26 +469,24 @@ module PgHero
|
|
470
469
|
end
|
471
470
|
|
472
471
|
def explain_safe?
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
true
|
478
|
-
end
|
472
|
+
select_all("SELECT 1; SELECT 1")
|
473
|
+
false
|
474
|
+
rescue ActiveRecord::StatementInvalid
|
475
|
+
true
|
479
476
|
end
|
480
477
|
|
481
478
|
def settings
|
482
|
-
names = %w
|
479
|
+
names = %w(
|
483
480
|
max_connections shared_buffers effective_cache_size work_mem
|
484
481
|
maintenance_work_mem checkpoint_segments checkpoint_completion_target
|
485
482
|
wal_buffers default_statistics_target
|
486
|
-
|
487
|
-
values = Hash[
|
488
|
-
Hash[
|
483
|
+
)
|
484
|
+
values = Hash[select_all(Connection.send(:sanitize_sql_array, ["SELECT name, setting, unit FROM pg_settings WHERE name IN (?)", names])).sort_by { |row| names.index(row["name"]) }.map { |row| [row["name"], friendly_value(row["setting"], row["unit"])] }]
|
485
|
+
Hash[names.map { |name| [name, values[name]] }]
|
489
486
|
end
|
490
487
|
|
491
488
|
def friendly_value(setting, unit)
|
492
|
-
if %w
|
489
|
+
if %w(kB 8kB).include?(unit)
|
493
490
|
value = setting.to_i
|
494
491
|
value *= 8 if unit == "8kB"
|
495
492
|
|
@@ -520,8 +517,7 @@ module PgHero
|
|
520
517
|
|
521
518
|
# from ActiveSupport
|
522
519
|
def squish(str)
|
523
|
-
str.to_s.gsub(/\A[[:space:]]+/,
|
520
|
+
str.to_s.gsub(/\A[[:space:]]+/, "").gsub(/[[:space:]]+\z/, "").gsub(/[[:space:]]+/, " ")
|
524
521
|
end
|
525
|
-
|
526
522
|
end
|
527
523
|
end
|
data/lib/pghero/version.rb
CHANGED
data/pghero.gemspec
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "pghero/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "pghero"
|
8
8
|
spec.version = PgHero::VERSION
|
9
9
|
spec.authors = ["Andrew Kane"]
|
10
10
|
spec.email = ["andrew@chartkick.com"]
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
11
|
+
spec.summary = "Database insights made easy"
|
12
|
+
spec.description = "Database insights made easy"
|
13
13
|
spec.homepage = "https://github.com/ankane/pghero"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
data/test/pghero_test.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require_relative "test_helper"
|
2
2
|
|
3
3
|
class TestPgHero < Minitest::Test
|
4
|
-
|
5
4
|
def setup
|
6
5
|
User.delete_all
|
7
6
|
end
|
@@ -14,7 +13,6 @@ class TestPgHero < Minitest::Test
|
|
14
13
|
|
15
14
|
def test_explain_multiple_statements
|
16
15
|
User.create!
|
17
|
-
assert_raises(ActiveRecord::StatementInvalid){ PgHero.explain("ANALYZE DELETE FROM users; DELETE FROM users; COMMIT") }
|
16
|
+
assert_raises(ActiveRecord::StatementInvalid) { PgHero.explain("ANALYZE DELETE FROM users; DELETE FROM users; COMMIT") }
|
18
17
|
end
|
19
|
-
|
20
18
|
end
|
data/test/test_helper.rb
CHANGED
@@ -8,7 +8,7 @@ Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
|
|
8
8
|
|
9
9
|
ActiveRecord::Base.establish_connection adapter: "postgresql", database: "pghero_test"
|
10
10
|
|
11
|
-
ActiveRecord::Migration.create_table :users, force: true do
|
11
|
+
ActiveRecord::Migration.create_table :users, force: true do
|
12
12
|
# just id
|
13
13
|
end
|
14
14
|
|
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: 0.1.
|
4
|
+
version: 0.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -95,8 +95,10 @@ files:
|
|
95
95
|
- Rakefile
|
96
96
|
- app/controllers/pg_hero/home_controller.rb
|
97
97
|
- app/views/layouts/pg_hero/application.html.erb
|
98
|
+
- app/views/pg_hero/home/_connections_table.html.erb
|
98
99
|
- app/views/pg_hero/home/_queries_table.html.erb
|
99
100
|
- app/views/pg_hero/home/_query_stats_table.html.erb
|
101
|
+
- app/views/pg_hero/home/connections.html.erb
|
100
102
|
- app/views/pg_hero/home/explain.html.erb
|
101
103
|
- app/views/pg_hero/home/index.html.erb
|
102
104
|
- app/views/pg_hero/home/indexes.html.erb
|
@@ -142,3 +144,4 @@ summary: Database insights made easy
|
|
142
144
|
test_files:
|
143
145
|
- test/pghero_test.rb
|
144
146
|
- test/test_helper.rb
|
147
|
+
has_rdoc:
|