pghero 0.1.0 → 0.1.1
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/.gitignore +1 -1
- data/CHANGELOG.md +6 -0
- data/README.md +7 -5
- data/Rakefile +6 -0
- data/app/controllers/pg_hero/home_controller.rb +48 -0
- data/app/views/layouts/pg_hero/application.html.erb +27 -5
- data/app/views/pg_hero/home/_query_stats_table.html.erb +21 -0
- data/app/views/pg_hero/home/explain.html.erb +15 -0
- data/app/views/pg_hero/home/index.html.erb +47 -0
- data/app/views/pg_hero/home/queries.html.erb +1 -1
- data/app/views/pg_hero/home/query_stats.html.erb +17 -0
- data/config/routes.rb +5 -0
- data/gemfiles/activerecord31.gemfile +6 -0
- data/gemfiles/activerecord32.gemfile +6 -0
- data/gemfiles/activerecord40.gemfile +6 -0
- data/lib/pghero.rb +126 -2
- data/lib/pghero/version.rb +1 -1
- data/pghero.gemspec +4 -0
- data/test/pghero_test.rb +20 -0
- data/test/test_helper.rb +16 -0
- metadata +55 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 924297f7d5d7f5ae69f7105d22752011a809c6e3
|
4
|
+
data.tar.gz: 8fcd272469562fd914aa9ad490a6cb1ba022ad56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b83d39c5c5939966dfffbe4c0f6013a1f0830e8cf12821d9c46460a0ab8e55b82a0ba1273159a0c3c309d0259ae50060cb4538de4dddd0cf69918d1f8586f881
|
7
|
+
data.tar.gz: 2932ffba7ddd3545085858e3ec26a08a1d5c119a80fd8ee297ef947335128e0210ee5450b6d0345c23861d97978c6ec2408536629e19e4d4e82398f74b8e28fa
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -4,13 +4,13 @@ Database insights made easy
|
|
4
4
|
|
5
5
|
[View the demo](https://pghero.herokuapp.com/)
|
6
6
|
|
7
|
-
![Screenshot](https://pghero.herokuapp.com/assets/screenshot-
|
7
|
+
![Screenshot](https://pghero.herokuapp.com/assets/screenshot-57d07895ddb050f8dd46b98e73ef1415.png)
|
8
8
|
|
9
9
|
Supports PostgreSQL 9.2+
|
10
10
|
|
11
11
|
For pure SQL, check out [PgHero.sql](https://github.com/ankane/pghero.sql)
|
12
12
|
|
13
|
-
|
13
|
+
A big thanks to [Craig Kerstiens](http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/) and [Heroku](https://blog.heroku.com/archives/2013/5/10/more_insight_into_your_database_with_pgextras) for the initial queries :clap:
|
14
14
|
|
15
15
|
## Installation
|
16
16
|
|
@@ -68,13 +68,15 @@ end
|
|
68
68
|
|
69
69
|
## TODO
|
70
70
|
|
71
|
-
-
|
71
|
+
- show exactly which indexes to add
|
72
72
|
- use `pg_stat_statements` extension for more detailed insights
|
73
|
-
-
|
73
|
+
- more detailed explanations on dashboard
|
74
|
+
|
75
|
+
Know a bit about PostgreSQL? [Suggestions](https://github.com/ankane/pghero/issues) are greatly appreciated.
|
74
76
|
|
75
77
|
## Thanks
|
76
78
|
|
77
|
-
Thanks to [Heroku](https://blog.heroku.com/archives/2013/5/10/more_insight_into_your_database_with_pgextras) for the initial queries and [Bootswatch](https://github.com/thomaspark/bootswatch) for the theme.
|
79
|
+
Thanks to [Craig Kerstiens](http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/) and [Heroku](https://blog.heroku.com/archives/2013/5/10/more_insight_into_your_database_with_pgextras) for the initial queries and [Bootswatch](https://github.com/thomaspark/bootswatch) for the theme.
|
78
80
|
|
79
81
|
## History
|
80
82
|
|
data/Rakefile
CHANGED
@@ -6,14 +6,19 @@ module PgHero
|
|
6
6
|
|
7
7
|
http_basic_authenticate_with name: ENV["PGHERO_USERNAME"], password: ENV["PGHERO_PASSWORD"] if ENV["PGHERO_PASSWORD"]
|
8
8
|
|
9
|
+
before_filter :set_query_stats_enabled
|
10
|
+
|
9
11
|
def index
|
10
12
|
@title = "Status"
|
13
|
+
@slow_queries = PgHero.slow_queries
|
11
14
|
@long_running_queries = PgHero.long_running_queries
|
12
15
|
@index_hit_rate = PgHero.index_hit_rate
|
13
16
|
@table_hit_rate = PgHero.table_hit_rate
|
14
17
|
@missing_indexes = PgHero.missing_indexes
|
15
18
|
@unused_indexes = PgHero.unused_indexes
|
16
19
|
@good_cache_rate = @table_hit_rate >= 0.99 && @index_hit_rate >= 0.99
|
20
|
+
@query_stats_available = PgHero.query_stats_available?
|
21
|
+
@rds = PgHero.rds?
|
17
22
|
end
|
18
23
|
|
19
24
|
def indexes
|
@@ -32,6 +37,25 @@ module PgHero
|
|
32
37
|
@running_queries = PgHero.running_queries
|
33
38
|
end
|
34
39
|
|
40
|
+
def query_stats
|
41
|
+
@title = "Query Stats"
|
42
|
+
@query_stats = PgHero.query_stats
|
43
|
+
end
|
44
|
+
|
45
|
+
def explain
|
46
|
+
@title = "Explain"
|
47
|
+
@query = params[:query]
|
48
|
+
# TODO use get + token instead of post so users can share links
|
49
|
+
# need to prevent CSRF and DoS
|
50
|
+
if request.post? and @query
|
51
|
+
begin
|
52
|
+
@explanation = PgHero.explain(@query)
|
53
|
+
rescue ActiveRecord::StatementInvalid => e
|
54
|
+
@error = e.message
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
35
59
|
def kill
|
36
60
|
if PgHero.kill(params[:pid])
|
37
61
|
redirect_to root_path, notice: "Query killed"
|
@@ -45,5 +69,29 @@ module PgHero
|
|
45
69
|
redirect_to :back, notice: "Connections killed"
|
46
70
|
end
|
47
71
|
|
72
|
+
def enable_query_stats
|
73
|
+
begin
|
74
|
+
PgHero.enable_query_stats
|
75
|
+
redirect_to :back, notice: "Query stats enabled"
|
76
|
+
rescue ActiveRecord::StatementInvalid => e
|
77
|
+
redirect_to :back, alert: "The database user does not have permission to enable query stats"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def reset_query_stats
|
82
|
+
begin
|
83
|
+
PgHero.reset_query_stats
|
84
|
+
redirect_to :back, notice: "Query stats reset"
|
85
|
+
rescue ActiveRecord::StatementInvalid => e
|
86
|
+
redirect_to :back, alert: "The database user does not have permission to reset query stats"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def set_query_stats_enabled
|
93
|
+
@query_stats_enabled = PgHero.query_stats_enabled?
|
94
|
+
end
|
95
|
+
|
48
96
|
end
|
49
97
|
end
|
@@ -7,13 +7,16 @@
|
|
7
7
|
|
8
8
|
<style>
|
9
9
|
body {
|
10
|
-
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
11
10
|
margin: 0;
|
12
11
|
padding: 20px;
|
12
|
+
background-color: #2b3e50;
|
13
|
+
}
|
14
|
+
|
15
|
+
body, textarea {
|
16
|
+
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
13
17
|
font-size: 14px;
|
14
18
|
line-height: 1.4;
|
15
19
|
color: #333;
|
16
|
-
background-color: #2b3e50;
|
17
20
|
}
|
18
21
|
|
19
22
|
a, a:visited, a:active {
|
@@ -67,6 +70,13 @@
|
|
67
70
|
white-space: pre-wrap;
|
68
71
|
}
|
69
72
|
|
73
|
+
textarea {
|
74
|
+
width: 100%;
|
75
|
+
height: 80px;
|
76
|
+
border: solid 1px #ccc;
|
77
|
+
padding: 10px;
|
78
|
+
}
|
79
|
+
|
70
80
|
.container {
|
71
81
|
max-width: 1000px;
|
72
82
|
margin-left: auto;
|
@@ -124,6 +134,8 @@
|
|
124
134
|
cursor: pointer;
|
125
135
|
outline: none;
|
126
136
|
margin: 0;
|
137
|
+
-webkit-appearance: none;
|
138
|
+
-webkit-border-radius: 0;
|
127
139
|
}
|
128
140
|
|
129
141
|
.btn-danger {
|
@@ -153,6 +165,10 @@
|
|
153
165
|
background-color: #f0ad4e;
|
154
166
|
}
|
155
167
|
|
168
|
+
.alert-danger {
|
169
|
+
background-color: #df691a;
|
170
|
+
}
|
171
|
+
|
156
172
|
.alert a {
|
157
173
|
color: #fff;
|
158
174
|
text-decoration: none;
|
@@ -312,8 +328,10 @@
|
|
312
328
|
</head>
|
313
329
|
<body>
|
314
330
|
<div class="container">
|
315
|
-
<div class="alert alert
|
316
|
-
<% if
|
331
|
+
<div class="alert alert-<%= alert ? "danger" : "info" %>">
|
332
|
+
<% if alert %>
|
333
|
+
<%= alert %>
|
334
|
+
<% elsif notice %>
|
317
335
|
<%= notice %>
|
318
336
|
<% elsif Rails.env.development? %>
|
319
337
|
Do not use development information to make decisions about your production environment
|
@@ -327,9 +345,13 @@
|
|
327
345
|
<ul class="nav">
|
328
346
|
<!-- poor man's active_link_to -->
|
329
347
|
<li class="<%= controller.action_name == "index" ? "active" : "" %>"><%= link_to "Status", root_path %></li>
|
348
|
+
<% if @query_stats_enabled %>
|
349
|
+
<li class="<%= controller.action_name == "query_stats" ? "active" : "" %>"><%= link_to "Queries", query_stats_path %></li>
|
350
|
+
<% end %>
|
330
351
|
<li class="<%= controller.action_name == "indexes" ? "active" : "" %>"><%= link_to "Indexes", indexes_path %></li>
|
331
352
|
<li class="<%= controller.action_name == "space" ? "active" : "" %>"><%= link_to "Space", space_path %></li>
|
332
|
-
<li class="<%= controller.action_name == "
|
353
|
+
<li class="<%= controller.action_name == "explain" ? "active" : "" %>"><%= link_to "Explain", explain_path %></li>
|
354
|
+
<li class="<%= controller.action_name == "queries" ? "active" : "" %>"><%= link_to "Live Queries", queries_path %></li>
|
333
355
|
</ul>
|
334
356
|
</div>
|
335
357
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<table class="table">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th style="width: 33.33%;">Total Time</th>
|
5
|
+
<th style="width: 33.33%;">Average Time</th>
|
6
|
+
<th style="width: 33.33%;">Calls</th>
|
7
|
+
</tr>
|
8
|
+
</thead>
|
9
|
+
<tbody>
|
10
|
+
<% queries.each do |query| %>
|
11
|
+
<tr>
|
12
|
+
<td><%= query["total_minutes"].to_f.round %> min</td>
|
13
|
+
<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>
|
15
|
+
</tr>
|
16
|
+
<tr>
|
17
|
+
<td colspan="3" style="border-top: none; padding: 0;"><pre><%= query["query"] %></pre></td>
|
18
|
+
</tr>
|
19
|
+
<% end %>
|
20
|
+
</tbody>
|
21
|
+
</table>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<h1>Explain</h1>
|
3
|
+
|
4
|
+
<%= form_tag explain_path do %>
|
5
|
+
<%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %>
|
6
|
+
<p><%= submit_tag "Explain", class: "btn btn-info" %></p>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<% if @explanation %>
|
10
|
+
<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>
|
12
|
+
<% elsif @error %>
|
13
|
+
<div class="alert alert-danger"><%= @error %></div>
|
14
|
+
<% end %>
|
15
|
+
</div>
|
@@ -1,4 +1,20 @@
|
|
1
1
|
<div id="status">
|
2
|
+
<div class="alert alert-<%= @query_stats_enabled ? "success" : "warning" %>">
|
3
|
+
<% if @query_stats_enabled %>
|
4
|
+
Query stats are enabled
|
5
|
+
<% else %>
|
6
|
+
Query stats are not enabled
|
7
|
+
<% end %>
|
8
|
+
</div>
|
9
|
+
<div class="alert alert-<%= @query_stats_enabled && @slow_queries.empty? ? "success" : "warning" %>">
|
10
|
+
<% if !@query_stats_enabled %>
|
11
|
+
Query stats must be enabled for slow queries
|
12
|
+
<% elsif @slow_queries.any? %>
|
13
|
+
<%= pluralize(@slow_queries.size, "slow query") %>
|
14
|
+
<% else %>
|
15
|
+
No slow queries
|
16
|
+
<% end %>
|
17
|
+
</div>
|
2
18
|
<div class="alert alert-<%= @long_running_queries.empty? ? "success" : "warning" %>">
|
3
19
|
<% if @long_running_queries.any? %>
|
4
20
|
<%= pluralize(@long_running_queries.size, "long running query") %>
|
@@ -29,6 +45,37 @@
|
|
29
45
|
</div>
|
30
46
|
</div>
|
31
47
|
|
48
|
+
<% if !@query_stats_enabled %>
|
49
|
+
<div class="content">
|
50
|
+
<h1>Query Statistics</h1>
|
51
|
+
|
52
|
+
<% if @query_stats_available %>
|
53
|
+
<p>
|
54
|
+
Query statistics are available but not enabled.
|
55
|
+
<%= button_to "Enable", enable_query_stats_path, class: "btn btn-info" %>
|
56
|
+
</p>
|
57
|
+
<% elsif @rds %>
|
58
|
+
<p>Query statistics are not available on Amazon RDS. <%= link_to "Tell Amazon you want this.", "https://forums.aws.amazon.com/thread.jspa?messageID=548724", target: "_blank" %></p>
|
59
|
+
<% else %>
|
60
|
+
<p>Make them available by adding the following lines to <code>postgresql.conf</code>:</p>
|
61
|
+
<pre>shared_preload_libraries = 'pg_stat_statements'
|
62
|
+
pg_stat_statements.track = all</pre>
|
63
|
+
<p>Restart the server for the changes to take effect.</p>
|
64
|
+
<% end %>
|
65
|
+
</div>
|
66
|
+
<% end %>
|
67
|
+
|
68
|
+
<% if @query_stats_enabled && @slow_queries.any? %>
|
69
|
+
<div class="content">
|
70
|
+
<h1>Slow Queries</h1>
|
71
|
+
|
72
|
+
<p>Slow queries take 20 ms or more on average and have been called at least 100 times.</p>
|
73
|
+
<p><%= link_to "Run EXPLAIN", explain_path %> on queries to see where to add indexes.</p>
|
74
|
+
|
75
|
+
<%= render partial: "query_stats_table", locals: {queries: @slow_queries} %>
|
76
|
+
</div>
|
77
|
+
<% end %>
|
78
|
+
|
32
79
|
<% if @long_running_queries.any? %>
|
33
80
|
<div class="content">
|
34
81
|
<h1>Long Running Queries</h1>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<% if @query_stats_enabled %>
|
3
|
+
<%= button_to "Reset", reset_query_stats_path, class: "btn btn-danger", style: "float: right;" %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<h1>Queries</h1>
|
7
|
+
|
8
|
+
<% if @query_stats_enabled %>
|
9
|
+
<% if @query_stats.any? %>
|
10
|
+
<%= render partial: "query_stats_table", locals: {queries: @query_stats} %>
|
11
|
+
<% else %>
|
12
|
+
<p>Stats are not available yet. Come back soon!</p>
|
13
|
+
<% end %>
|
14
|
+
<% else %>
|
15
|
+
<p>Query stats are not enabled.</p>
|
16
|
+
<% end %>
|
17
|
+
</div>
|
data/config/routes.rb
CHANGED
@@ -3,7 +3,12 @@ PgHero::Engine.routes.draw do
|
|
3
3
|
get "indexes", to: "home#indexes"
|
4
4
|
get "space", to: "home#space"
|
5
5
|
get "queries", to: "home#queries"
|
6
|
+
get "query_stats", to: "home#query_stats"
|
7
|
+
get "explain", to: "home#explain"
|
6
8
|
|
7
9
|
post "kill", to: "home#kill"
|
8
10
|
post "kill_all", to: "home#kill_all"
|
11
|
+
post "enable_query_stats", to: "home#enable_query_stats"
|
12
|
+
post "explain", to: "home#explain"
|
13
|
+
post "reset_query_stats", to: "home#reset_query_stats"
|
9
14
|
end
|
data/lib/pghero.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "pghero/version"
|
2
|
+
require "active_record"
|
2
3
|
require "pghero/engine" if defined?(Rails)
|
3
4
|
|
4
5
|
module PgHero
|
@@ -169,7 +170,7 @@ module PgHero
|
|
169
170
|
end
|
170
171
|
|
171
172
|
def kill(pid)
|
172
|
-
|
173
|
+
execute("SELECT pg_cancel_backend(#{pid.to_i})").first["pg_cancel_backend"] == "t"
|
173
174
|
end
|
174
175
|
|
175
176
|
def kill_all
|
@@ -185,14 +186,137 @@ module PgHero
|
|
185
186
|
true
|
186
187
|
end
|
187
188
|
|
189
|
+
# http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/
|
190
|
+
def query_stats
|
191
|
+
if query_stats_enabled?
|
192
|
+
select_all %Q{
|
193
|
+
SELECT
|
194
|
+
query,
|
195
|
+
(total_time / 1000 / 60) as total_minutes,
|
196
|
+
(total_time / calls) as average_time,
|
197
|
+
calls
|
198
|
+
FROM
|
199
|
+
pg_stat_statements
|
200
|
+
INNER JOIN
|
201
|
+
pg_database ON pg_database.oid = pg_stat_statements.dbid
|
202
|
+
WHERE
|
203
|
+
pg_database.datname = current_database()
|
204
|
+
ORDER BY
|
205
|
+
total_minutes DESC
|
206
|
+
LIMIT 100
|
207
|
+
}
|
208
|
+
else
|
209
|
+
[]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def slow_queries
|
214
|
+
if query_stats_enabled?
|
215
|
+
select_all %Q{
|
216
|
+
SELECT
|
217
|
+
query,
|
218
|
+
(total_time / 1000 / 60) as total_minutes,
|
219
|
+
(total_time / calls) as average_time,
|
220
|
+
calls
|
221
|
+
FROM
|
222
|
+
pg_stat_statements
|
223
|
+
INNER JOIN
|
224
|
+
pg_database ON pg_database.oid = pg_stat_statements.dbid
|
225
|
+
WHERE
|
226
|
+
pg_database.datname = current_database()
|
227
|
+
AND calls >= 100
|
228
|
+
AND (total_time / calls) >= 20
|
229
|
+
ORDER BY
|
230
|
+
total_minutes DESC
|
231
|
+
LIMIT 100
|
232
|
+
}
|
233
|
+
else
|
234
|
+
[]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def query_stats_available?
|
239
|
+
select_all("SELECT COUNT(*) AS count FROM pg_available_extensions WHERE name = 'pg_stat_statements'").first["count"].to_i > 0
|
240
|
+
end
|
241
|
+
|
242
|
+
def query_stats_enabled?
|
243
|
+
select_all("SELECT COUNT(*) AS count FROM pg_extension WHERE extname = 'pg_stat_statements'").first["count"].to_i > 0 && query_stats_readable?
|
244
|
+
end
|
245
|
+
|
246
|
+
def query_stats_readable?
|
247
|
+
begin
|
248
|
+
# ensure the user has access to the table
|
249
|
+
select_all("SELECT has_table_privilege(current_user, 'pg_stat_statements', 'SELECT')").first["has_table_privilege"] == "t"
|
250
|
+
rescue ActiveRecord::StatementInvalid
|
251
|
+
false
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def enable_query_stats
|
256
|
+
execute("CREATE EXTENSION pg_stat_statements")
|
257
|
+
end
|
258
|
+
|
259
|
+
def disable_query_stats
|
260
|
+
execute("DROP EXTENSION IF EXISTS pg_stat_statements")
|
261
|
+
true
|
262
|
+
end
|
263
|
+
|
264
|
+
def reset_query_stats
|
265
|
+
if query_stats_enabled?
|
266
|
+
execute("SELECT pg_stat_statements_reset()")
|
267
|
+
true
|
268
|
+
else
|
269
|
+
false
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def rds?
|
274
|
+
!!(Connection.connection_config[:host].to_s =~ /rds\.amazonaws\.com\z/)
|
275
|
+
end
|
276
|
+
|
277
|
+
def explain(sql)
|
278
|
+
sql = squish(sql)
|
279
|
+
explanation = nil
|
280
|
+
explain_safe = explain_safe?
|
281
|
+
|
282
|
+
# use transaction for safety
|
283
|
+
Connection.transaction do
|
284
|
+
if !explain_safe and (sql.sub(/;\z/, "").include?(";") or sql.upcase.include?("COMMIT"))
|
285
|
+
raise ActiveRecord::StatementInvalid, "Unsafe statement"
|
286
|
+
end
|
287
|
+
explanation = select_all("EXPLAIN #{sql}").map{|v| v["QUERY PLAN"] }.join("\n")
|
288
|
+
raise ActiveRecord::Rollback
|
289
|
+
end
|
290
|
+
|
291
|
+
explanation
|
292
|
+
end
|
293
|
+
|
294
|
+
def explain_safe?
|
295
|
+
begin
|
296
|
+
select_all("SELECT 1; SELECT 1")
|
297
|
+
false
|
298
|
+
rescue ActiveRecord::StatementInvalid
|
299
|
+
true
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
188
303
|
def select_all(sql)
|
189
304
|
# squish for logs
|
190
|
-
connection.select_all(sql
|
305
|
+
connection.select_all(squish(sql)).to_a
|
306
|
+
end
|
307
|
+
|
308
|
+
def execute(sql)
|
309
|
+
connection.execute(sql)
|
191
310
|
end
|
192
311
|
|
193
312
|
def connection
|
194
313
|
@connection ||= Connection.connection
|
195
314
|
end
|
196
315
|
|
316
|
+
# from ActiveSupport
|
317
|
+
def squish(str)
|
318
|
+
str.to_s.gsub(/\A[[:space:]]+/, '').gsub(/[[:space:]]+\z/, '').gsub(/[[:space:]]+/, ' ')
|
319
|
+
end
|
320
|
+
|
197
321
|
end
|
198
322
|
end
|
data/lib/pghero/version.rb
CHANGED
data/pghero.gemspec
CHANGED
@@ -18,6 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "pg"
|
22
|
+
spec.add_dependency "activerecord"
|
23
|
+
|
21
24
|
spec.add_development_dependency "bundler", "~> 1.6"
|
22
25
|
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "minitest"
|
23
27
|
end
|
data/test/pghero_test.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class TestPgHero < Minitest::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
User.delete_all
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_explain
|
10
|
+
User.create!
|
11
|
+
PgHero.explain("ANALYZE DELETE FROM users")
|
12
|
+
assert_equal 1, User.count
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_explain_multiple_statements
|
16
|
+
User.create!
|
17
|
+
assert_raises(ActiveRecord::StatementInvalid){ PgHero.explain("ANALYZE DELETE FROM users; DELETE FROM users; COMMIT") }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
Bundler.require(:default)
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "minitest/pride"
|
5
|
+
|
6
|
+
# for Minitest < 5
|
7
|
+
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
|
8
|
+
|
9
|
+
ActiveRecord::Base.establish_connection adapter: "postgresql", database: "pghero_test"
|
10
|
+
|
11
|
+
ActiveRecord::Migration.create_table :users, force: true do |t|
|
12
|
+
# just id
|
13
|
+
end
|
14
|
+
|
15
|
+
class User < ActiveRecord::Base
|
16
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
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.1
|
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
|
+
date: 2014-08-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pg
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: bundler
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +66,20 @@ dependencies:
|
|
38
66
|
- - ">="
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
41
83
|
description: Database insights made easy
|
42
84
|
email:
|
43
85
|
- andrew@chartkick.com
|
@@ -54,15 +96,23 @@ files:
|
|
54
96
|
- app/controllers/pg_hero/home_controller.rb
|
55
97
|
- app/views/layouts/pg_hero/application.html.erb
|
56
98
|
- app/views/pg_hero/home/_queries_table.html.erb
|
99
|
+
- app/views/pg_hero/home/_query_stats_table.html.erb
|
100
|
+
- app/views/pg_hero/home/explain.html.erb
|
57
101
|
- app/views/pg_hero/home/index.html.erb
|
58
102
|
- app/views/pg_hero/home/indexes.html.erb
|
59
103
|
- app/views/pg_hero/home/queries.html.erb
|
104
|
+
- app/views/pg_hero/home/query_stats.html.erb
|
60
105
|
- app/views/pg_hero/home/space.html.erb
|
61
106
|
- config/routes.rb
|
107
|
+
- gemfiles/activerecord31.gemfile
|
108
|
+
- gemfiles/activerecord32.gemfile
|
109
|
+
- gemfiles/activerecord40.gemfile
|
62
110
|
- lib/pghero.rb
|
63
111
|
- lib/pghero/engine.rb
|
64
112
|
- lib/pghero/version.rb
|
65
113
|
- pghero.gemspec
|
114
|
+
- test/pghero_test.rb
|
115
|
+
- test/test_helper.rb
|
66
116
|
homepage: https://github.com/ankane/pghero
|
67
117
|
licenses:
|
68
118
|
- MIT
|
@@ -87,5 +137,7 @@ rubygems_version: 2.2.2
|
|
87
137
|
signing_key:
|
88
138
|
specification_version: 4
|
89
139
|
summary: Database insights made easy
|
90
|
-
test_files:
|
140
|
+
test_files:
|
141
|
+
- test/pghero_test.rb
|
142
|
+
- test/test_helper.rb
|
91
143
|
has_rdoc:
|