pghero 0.1.7 → 0.1.8
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/README.md +11 -0
- data/app/controllers/pg_hero/home_controller.rb +3 -0
- data/app/views/layouts/pg_hero/application.html.erb +6 -0
- data/app/views/pg_hero/home/_query_stats_table.html.erb +14 -2
- data/app/views/pg_hero/home/index.html.erb +34 -0
- data/app/views/pg_hero/home/system_stats.html.erb +5 -2
- data/lib/pghero.rb +100 -47
- data/lib/pghero/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e9922be6c95f3dd56d5b41844d239316ce99f8e
|
4
|
+
data.tar.gz: 53c0c88d93f9794ecc698601659695e65bc05cb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d00b0e9a93e26343ee7418279778a0e7144af7eae862f4a17edcf3be93af28f2ff76c3cb20c2570422e93f98a430394029ab1f47c2b6b861bac75aec121812e5
|
7
|
+
data.tar.gz: f0053ca3dc71a1de4bee814722033dae44ed4484fc07fb4f68aff4abeff929f90587668d5c91bd7fdd4c777e7929d4446d3510899b65f948ecc1c4f3f82f8795
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -43,6 +43,7 @@ PgHero.database_size
|
|
43
43
|
PgHero.relation_sizes
|
44
44
|
PgHero.index_hit_rate
|
45
45
|
PgHero.table_hit_rate
|
46
|
+
PgHero.total_connections
|
46
47
|
```
|
47
48
|
|
48
49
|
Kill queries
|
@@ -124,6 +125,8 @@ Add the following to your `postgresql.conf`:
|
|
124
125
|
```conf
|
125
126
|
shared_preload_libraries = 'pg_stat_statements'
|
126
127
|
pg_stat_statements.track = all
|
128
|
+
pg_stat_statements.max = 10000
|
129
|
+
track_activity_query_size = 2048
|
127
130
|
```
|
128
131
|
|
129
132
|
Then restart PostgreSQL. As a superuser from the `psql` console, run:
|
@@ -166,6 +169,10 @@ and reset stats with:
|
|
166
169
|
SELECT pg_stat_statements_reset();
|
167
170
|
```
|
168
171
|
|
172
|
+
#### Queries show up as `<insufficient privilege>`
|
173
|
+
|
174
|
+
For security reasons, only superusers can see queries executed by other users.
|
175
|
+
|
169
176
|
## System Stats
|
170
177
|
|
171
178
|
CPU usage is available for Amazon RDS. Add these lines to your application’s Gemfile:
|
@@ -203,6 +210,10 @@ Minimum calls for slow queries
|
|
203
210
|
PgHero.slow_query_calls = 100 # default
|
204
211
|
```
|
205
212
|
|
213
|
+
## Bonus
|
214
|
+
|
215
|
+
See where queries come from with [Marginalia](https://github.com/basecamp/marginalia) - comments appear on the Live Queries tab.
|
216
|
+
|
206
217
|
## TODO
|
207
218
|
|
208
219
|
- show exactly which indexes to add
|
@@ -18,6 +18,8 @@ module PgHero
|
|
18
18
|
@unused_indexes = PgHero.unused_indexes
|
19
19
|
@good_cache_rate = @table_hit_rate >= 0.99 && @index_hit_rate >= 0.99
|
20
20
|
@query_stats_available = PgHero.query_stats_available?
|
21
|
+
@total_connections = PgHero.total_connections
|
22
|
+
@good_total_connections = @total_connections < PgHero.total_connections_threshold
|
21
23
|
end
|
22
24
|
|
23
25
|
def indexes
|
@@ -44,6 +46,7 @@ module PgHero
|
|
44
46
|
def system_stats
|
45
47
|
@title = "System Stats"
|
46
48
|
@cpu_usage = PgHero.cpu_usage
|
49
|
+
@connection_stats = PgHero.connection_stats
|
47
50
|
end
|
48
51
|
|
49
52
|
def explain
|
@@ -9,9 +9,21 @@
|
|
9
9
|
<tbody>
|
10
10
|
<% queries.each do |query| %>
|
11
11
|
<tr>
|
12
|
-
<td
|
12
|
+
<td>
|
13
|
+
<%= query["total_minutes"].to_f.round %> min
|
14
|
+
<span class="percent">
|
15
|
+
<% percent = query["total_percent"].to_f %>
|
16
|
+
<% if percent > 1 %>
|
17
|
+
<%= percent.round %>%
|
18
|
+
<% elsif percent > 0.1 %>
|
19
|
+
<%= percent.round(1) %>%
|
20
|
+
<% else %>
|
21
|
+
< 0.1%
|
22
|
+
<% end %>
|
23
|
+
</span>
|
24
|
+
</td>
|
13
25
|
<td><%= query["average_time"].to_f.round %> ms</td>
|
14
|
-
<td
|
26
|
+
<td><%= number_with_delimiter(query["calls"].to_i) %></td>
|
15
27
|
</tr>
|
16
28
|
<tr>
|
17
29
|
<td colspan="3" style="border-top: none; padding: 0;"><pre><%= query["query"] %></pre></td>
|
@@ -13,6 +13,13 @@
|
|
13
13
|
Low cache hit rate
|
14
14
|
<% end %>
|
15
15
|
</div>
|
16
|
+
<div class="alert alert-<%= @good_total_connections ? "success" : "warning" %>">
|
17
|
+
<% if @good_total_connections %>
|
18
|
+
Healthy number of connections
|
19
|
+
<% else %>
|
20
|
+
High number of connections
|
21
|
+
<% end %>
|
22
|
+
</div>
|
16
23
|
<div class="alert alert-<%= @query_stats_enabled && @slow_queries.empty? ? "success" : "warning" %>">
|
17
24
|
<% if !@query_stats_enabled %>
|
18
25
|
Query stats must be enabled for slow queries
|
@@ -63,6 +70,33 @@
|
|
63
70
|
</div>
|
64
71
|
<% end %>
|
65
72
|
|
73
|
+
<% if !@good_total_connections %>
|
74
|
+
<div class="content">
|
75
|
+
<h1>High Number of Connections</h1>
|
76
|
+
<p><%= pluralize(@total_connections, "connection") %></p>
|
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>
|
97
|
+
</div>
|
98
|
+
<% end %>
|
99
|
+
|
66
100
|
<% if !@query_stats_enabled %>
|
67
101
|
<div class="content">
|
68
102
|
<h1>Query Stats</h1>
|
@@ -1,6 +1,9 @@
|
|
1
|
+
<%= javascript_include_tag "//www.google.com/jsapi", "chartkick" %>
|
2
|
+
|
1
3
|
<div class="content">
|
2
4
|
<h1>CPU Usage</h1>
|
3
|
-
|
4
|
-
<%= javascript_include_tag "//www.google.com/jsapi", "chartkick" %>
|
5
5
|
<div style="margin-bottom: 20px;"><%= line_chart @cpu_usage, max: 101 %></div>
|
6
|
+
|
7
|
+
<h1>Connections</h1>
|
8
|
+
<div style="margin-bottom: 20px;"><%= line_chart @connection_stats %></div>
|
6
9
|
</div>
|
data/lib/pghero.rb
CHANGED
@@ -9,16 +9,17 @@ module PgHero
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class << self
|
12
|
-
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls
|
12
|
+
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :total_connections_threshold
|
13
13
|
end
|
14
14
|
self.long_running_query_sec = 60
|
15
15
|
self.slow_query_ms = 20
|
16
16
|
self.slow_query_calls = 100
|
17
|
+
self.total_connections_threshold = 100
|
17
18
|
|
18
19
|
class << self
|
19
20
|
|
20
21
|
def running_queries
|
21
|
-
select_all
|
22
|
+
select_all <<-SQL
|
22
23
|
SELECT
|
23
24
|
pid,
|
24
25
|
state,
|
@@ -35,11 +36,11 @@ module PgHero
|
|
35
36
|
AND pid <> pg_backend_pid()
|
36
37
|
ORDER BY
|
37
38
|
query_start DESC
|
38
|
-
|
39
|
+
SQL
|
39
40
|
end
|
40
41
|
|
41
42
|
def long_running_queries
|
42
|
-
select_all
|
43
|
+
select_all <<-SQL
|
43
44
|
SELECT
|
44
45
|
pid,
|
45
46
|
state,
|
@@ -57,11 +58,11 @@ module PgHero
|
|
57
58
|
AND now() - query_start > interval '#{long_running_query_sec.to_i} seconds'
|
58
59
|
ORDER BY
|
59
60
|
query_start DESC
|
60
|
-
|
61
|
+
SQL
|
61
62
|
end
|
62
63
|
|
63
64
|
def locks
|
64
|
-
select_all
|
65
|
+
select_all <<-SQL
|
65
66
|
SELECT DISTINCT ON (pid)
|
66
67
|
pg_stat_activity.pid,
|
67
68
|
pg_stat_activity.query,
|
@@ -77,29 +78,31 @@ module PgHero
|
|
77
78
|
ORDER BY
|
78
79
|
pid,
|
79
80
|
query_start
|
80
|
-
|
81
|
+
SQL
|
81
82
|
end
|
82
83
|
|
83
84
|
def index_hit_rate
|
84
|
-
select_all(
|
85
|
+
select_all(<<-SQL
|
85
86
|
SELECT
|
86
|
-
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS rate
|
87
|
+
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read), 0) AS rate
|
87
88
|
FROM
|
88
89
|
pg_statio_user_indexes
|
89
|
-
|
90
|
+
SQL
|
91
|
+
).first["rate"].to_f
|
90
92
|
end
|
91
93
|
|
92
94
|
def table_hit_rate
|
93
|
-
select_all(
|
95
|
+
select_all(<<-SQL
|
94
96
|
SELECT
|
95
|
-
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS rate
|
97
|
+
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) AS rate
|
96
98
|
FROM
|
97
99
|
pg_statio_user_tables
|
98
|
-
|
100
|
+
SQL
|
101
|
+
).first["rate"].to_f
|
99
102
|
end
|
100
103
|
|
101
104
|
def index_usage
|
102
|
-
select_all
|
105
|
+
select_all <<-SQL
|
103
106
|
SELECT
|
104
107
|
relname AS table,
|
105
108
|
CASE idx_scan
|
@@ -112,11 +115,11 @@ module PgHero
|
|
112
115
|
ORDER BY
|
113
116
|
n_live_tup DESC,
|
114
117
|
relname ASC
|
115
|
-
|
118
|
+
SQL
|
116
119
|
end
|
117
120
|
|
118
121
|
def missing_indexes
|
119
|
-
select_all
|
122
|
+
select_all <<-SQL
|
120
123
|
SELECT
|
121
124
|
relname AS table,
|
122
125
|
CASE idx_scan
|
@@ -133,11 +136,11 @@ module PgHero
|
|
133
136
|
ORDER BY
|
134
137
|
n_live_tup DESC,
|
135
138
|
relname ASC
|
136
|
-
|
139
|
+
SQL
|
137
140
|
end
|
138
141
|
|
139
142
|
def unused_tables
|
140
|
-
select_all
|
143
|
+
select_all <<-SQL
|
141
144
|
SELECT
|
142
145
|
relname AS table,
|
143
146
|
n_live_tup rows_in_table
|
@@ -148,11 +151,11 @@ module PgHero
|
|
148
151
|
ORDER BY
|
149
152
|
n_live_tup DESC,
|
150
153
|
relname ASC
|
151
|
-
|
154
|
+
SQL
|
152
155
|
end
|
153
156
|
|
154
157
|
def unused_indexes
|
155
|
-
select_all
|
158
|
+
select_all <<-SQL
|
156
159
|
SELECT
|
157
160
|
relname AS table,
|
158
161
|
indexrelname AS index,
|
@@ -169,11 +172,11 @@ module PgHero
|
|
169
172
|
ORDER BY
|
170
173
|
pg_relation_size(i.indexrelid) DESC,
|
171
174
|
relname ASC
|
172
|
-
|
175
|
+
SQL
|
173
176
|
end
|
174
177
|
|
175
178
|
def relation_sizes
|
176
|
-
select_all
|
179
|
+
select_all <<-SQL
|
177
180
|
SELECT
|
178
181
|
c.relname AS name,
|
179
182
|
CASE WHEN c.relkind = 'r' THEN 'table' ELSE 'index' END AS type,
|
@@ -189,19 +192,40 @@ module PgHero
|
|
189
192
|
ORDER BY
|
190
193
|
pg_table_size(c.oid) DESC,
|
191
194
|
name ASC
|
192
|
-
|
195
|
+
SQL
|
193
196
|
end
|
194
197
|
|
195
198
|
def database_size
|
196
199
|
select_all("SELECT pg_size_pretty(pg_database_size(current_database()))").first["pg_size_pretty"]
|
197
200
|
end
|
198
201
|
|
202
|
+
def total_connections
|
203
|
+
select_all("SELECT COUNT(*) FROM pg_stat_activity WHERE pid <> pg_backend_pid()").first["count"].to_i
|
204
|
+
end
|
205
|
+
|
206
|
+
def connection_sources
|
207
|
+
select_all <<-SQL
|
208
|
+
SELECT
|
209
|
+
application_name AS source,
|
210
|
+
COUNT(*) AS total_connections
|
211
|
+
FROM
|
212
|
+
pg_stat_activity
|
213
|
+
WHERE
|
214
|
+
pid <> pg_backend_pid()
|
215
|
+
GROUP BY
|
216
|
+
application_name
|
217
|
+
ORDER BY
|
218
|
+
COUNT(*) DESC,
|
219
|
+
application_name ASC
|
220
|
+
SQL
|
221
|
+
end
|
222
|
+
|
199
223
|
def kill(pid)
|
200
224
|
execute("SELECT pg_terminate_backend(#{pid.to_i})").first["pg_terminate_backend"] == "t"
|
201
225
|
end
|
202
226
|
|
203
227
|
def kill_all
|
204
|
-
select_all
|
228
|
+
select_all <<-SQL
|
205
229
|
SELECT
|
206
230
|
pg_terminate_backend(pid)
|
207
231
|
FROM
|
@@ -209,29 +233,39 @@ module PgHero
|
|
209
233
|
WHERE
|
210
234
|
pid <> pg_backend_pid()
|
211
235
|
AND query <> '<insufficient privilege>'
|
212
|
-
|
236
|
+
SQL
|
213
237
|
true
|
214
238
|
end
|
215
239
|
|
216
240
|
# http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/
|
217
241
|
def query_stats
|
218
242
|
if query_stats_enabled?
|
219
|
-
select_all
|
243
|
+
select_all <<-SQL
|
244
|
+
WITH query_stats AS (
|
245
|
+
SELECT
|
246
|
+
query,
|
247
|
+
(total_time / 1000 / 60) as total_minutes,
|
248
|
+
(total_time / calls) as average_time,
|
249
|
+
calls
|
250
|
+
FROM
|
251
|
+
pg_stat_statements
|
252
|
+
INNER JOIN
|
253
|
+
pg_database ON pg_database.oid = pg_stat_statements.dbid
|
254
|
+
WHERE
|
255
|
+
pg_database.datname = current_database()
|
256
|
+
)
|
220
257
|
SELECT
|
221
258
|
query,
|
222
|
-
|
223
|
-
|
224
|
-
calls
|
259
|
+
total_minutes,
|
260
|
+
average_time,
|
261
|
+
calls,
|
262
|
+
total_minutes * 100.0 / (SELECT SUM(total_minutes) FROM query_stats) AS total_percent
|
225
263
|
FROM
|
226
|
-
|
227
|
-
INNER JOIN
|
228
|
-
pg_database ON pg_database.oid = pg_stat_statements.dbid
|
229
|
-
WHERE
|
230
|
-
pg_database.datname = current_database()
|
264
|
+
query_stats
|
231
265
|
ORDER BY
|
232
266
|
total_minutes DESC
|
233
267
|
LIMIT 100
|
234
|
-
|
268
|
+
SQL
|
235
269
|
else
|
236
270
|
[]
|
237
271
|
end
|
@@ -239,24 +273,35 @@ module PgHero
|
|
239
273
|
|
240
274
|
def slow_queries
|
241
275
|
if query_stats_enabled?
|
242
|
-
select_all
|
276
|
+
select_all <<-SQL
|
277
|
+
WITH query_stats AS (
|
278
|
+
SELECT
|
279
|
+
query,
|
280
|
+
(total_time / 1000 / 60) as total_minutes,
|
281
|
+
(total_time / calls) as average_time,
|
282
|
+
calls
|
283
|
+
FROM
|
284
|
+
pg_stat_statements
|
285
|
+
INNER JOIN
|
286
|
+
pg_database ON pg_database.oid = pg_stat_statements.dbid
|
287
|
+
WHERE
|
288
|
+
pg_database.datname = current_database()
|
289
|
+
)
|
243
290
|
SELECT
|
244
291
|
query,
|
245
|
-
|
246
|
-
|
247
|
-
calls
|
292
|
+
total_minutes,
|
293
|
+
average_time,
|
294
|
+
calls,
|
295
|
+
total_minutes * 100.0 / (SELECT SUM(total_minutes) FROM query_stats) AS total_percent
|
248
296
|
FROM
|
249
|
-
|
250
|
-
INNER JOIN
|
251
|
-
pg_database ON pg_database.oid = pg_stat_statements.dbid
|
297
|
+
query_stats
|
252
298
|
WHERE
|
253
|
-
|
254
|
-
AND
|
255
|
-
AND (total_time / calls) >= #{slow_query_ms.to_i}
|
299
|
+
calls >= #{slow_query_calls.to_i}
|
300
|
+
AND average_time >= #{slow_query_ms.to_i}
|
256
301
|
ORDER BY
|
257
302
|
total_minutes DESC
|
258
303
|
LIMIT 100
|
259
|
-
|
304
|
+
SQL
|
260
305
|
else
|
261
306
|
[]
|
262
307
|
end
|
@@ -298,12 +343,20 @@ module PgHero
|
|
298
343
|
end
|
299
344
|
|
300
345
|
def cpu_usage
|
346
|
+
rds_stats("CPUUtilization")
|
347
|
+
end
|
348
|
+
|
349
|
+
def connection_stats
|
350
|
+
rds_stats("DatabaseConnections")
|
351
|
+
end
|
352
|
+
|
353
|
+
def rds_stats(metric_name)
|
301
354
|
if system_stats_enabled?
|
302
355
|
cw = AWS::CloudWatch.new(access_key_id: access_key_id, secret_access_key: secret_access_key)
|
303
356
|
now = Time.now
|
304
357
|
resp = cw.client.get_metric_statistics(
|
305
358
|
namespace: "AWS/RDS",
|
306
|
-
metric_name:
|
359
|
+
metric_name: metric_name,
|
307
360
|
dimensions: [{name: "DBInstanceIdentifier", value: db_instance_identifier}],
|
308
361
|
start_time: (now - 1 * 3600).iso8601,
|
309
362
|
end_time: now.iso8601,
|
data/lib/pghero/version.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: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -134,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
134
|
version: '0'
|
135
135
|
requirements: []
|
136
136
|
rubyforge_project:
|
137
|
-
rubygems_version: 2.
|
137
|
+
rubygems_version: 2.4.5
|
138
138
|
signing_key:
|
139
139
|
specification_version: 4
|
140
140
|
summary: Database insights made easy
|