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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb091e2ecc7b76f41e0af306855d6df60f9ace12
4
- data.tar.gz: c82ae33d920f6b7862b8aa9cc46e7abe0fffa5c2
3
+ metadata.gz: 5e9922be6c95f3dd56d5b41844d239316ce99f8e
4
+ data.tar.gz: 53c0c88d93f9794ecc698601659695e65bc05cb8
5
5
  SHA512:
6
- metadata.gz: c24fd383eb9069a9445a2e826e65dbb5eaeb3e9a7ab03c202dc1acaf42ef866e52f6895a8f202043f16b82eb41cc8ad297773d8e57811e5b0e928052e3cf699e
7
- data.tar.gz: 0d7193d888ae03896d64080dfa14dd811fc6e8b7aea621c913b34f082a4baa7da113339aa3dfcca9828276034cf0c8c163599a73d5bb23ae5ede1dccca21d028
6
+ metadata.gz: d00b0e9a93e26343ee7418279778a0e7144af7eae862f4a17edcf3be93af28f2ff76c3cb20c2570422e93f98a430394029ab1f47c2b6b861bac75aec121812e5
7
+ data.tar.gz: f0053ca3dc71a1de4bee814722033dae44ed4484fc07fb4f68aff4abeff929f90587668d5c91bd7fdd4c777e7929d4446d3510899b65f948ecc1c4f3f82f8795
@@ -1,3 +1,9 @@
1
+ ## 0.1.8
2
+
3
+ - Added `total_percent` to `query_stats`
4
+ - Added `total_connections`
5
+ - Added `connection_stats` for Amazon RDS
6
+
1
7
  ## 0.1.7
2
8
 
3
9
  - Added support for pg_stat_statments on Amazon RDS
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
@@ -88,6 +88,12 @@
88
88
  color: #999;
89
89
  }
90
90
 
91
+ .percent {
92
+ color: #999;
93
+ font-size: 12px;
94
+ margin-left: 6px;
95
+ }
96
+
91
97
  #status, .content, .alert {
92
98
  margin-bottom: 20px;
93
99
  }
@@ -9,9 +9,21 @@
9
9
  <tbody>
10
10
  <% queries.each do |query| %>
11
11
  <tr>
12
- <td><%= query["total_minutes"].to_f.round %> min</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
+ &lt; 0.1%
22
+ <% end %>
23
+ </span>
24
+ </td>
13
25
  <td><%= query["average_time"].to_f.round %> ms</td>
14
- <td><% calls = query["calls"].to_i %><%= calls >= 1000 ? number_with_delimiter(calls.round) + "k" : calls %></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>
@@ -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 %Q{
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 %Q{
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 %Q{
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(%Q{
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
- }).first["rate"].to_f
90
+ SQL
91
+ ).first["rate"].to_f
90
92
  end
91
93
 
92
94
  def table_hit_rate
93
- select_all(%Q{
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
- }).first["rate"].to_f
100
+ SQL
101
+ ).first["rate"].to_f
99
102
  end
100
103
 
101
104
  def index_usage
102
- select_all %Q{
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 %Q{
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 %Q{
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 %Q{
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 %Q{
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 %Q{
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 %Q{
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
- (total_time / 1000 / 60) as total_minutes,
223
- (total_time / calls) as average_time,
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
- pg_stat_statements
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 %Q{
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
- (total_time / 1000 / 60) as total_minutes,
246
- (total_time / calls) as average_time,
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
- pg_stat_statements
250
- INNER JOIN
251
- pg_database ON pg_database.oid = pg_stat_statements.dbid
297
+ query_stats
252
298
  WHERE
253
- pg_database.datname = current_database()
254
- AND calls >= #{slow_query_calls.to_i}
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: "CPUUtilization",
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,
@@ -1,3 +1,3 @@
1
1
  module PgHero
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
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.7
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: 2014-11-13 00:00:00.000000000 Z
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.2.2
137
+ rubygems_version: 2.4.5
138
138
  signing_key:
139
139
  specification_version: 4
140
140
  summary: Database insights made easy