pghero 0.0.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 +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +78 -0
- data/Rakefile +2 -0
- data/app/controllers/pg_hero/home_controller.rb +48 -0
- data/app/views/layouts/pg_hero/application.html.erb +73 -0
- data/app/views/pg_hero/home/_queries_table.html.erb +27 -0
- data/app/views/pg_hero/home/index.html.erb +101 -0
- data/app/views/pg_hero/home/indexes.html.erb +20 -0
- data/app/views/pg_hero/home/queries.html.erb +7 -0
- data/app/views/pg_hero/home/space.html.erb +20 -0
- data/config/routes.rb +9 -0
- data/lib/pghero.rb +166 -0
- data/lib/pghero/engine.rb +5 -0
- data/lib/pghero/version.rb +3 -0
- data/pghero.gemspec +23 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f98183aaa8abc79d3692c7a697fdbdacce963db5
|
4
|
+
data.tar.gz: a475c402a6e8e22ae5cb56aeab69ded88cec46e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1754f5562b4479fca325777469b3fb1c5f393616b29644c8158d89e0640bc3ccf3c3bf7ba7eeca8f81e720c6b7e44d226be65d25c9432d8b2f0ca61fc791da83
|
7
|
+
data.tar.gz: ebd54924ec01f64d71d3e37d0e6558d77838aa44b5a25b324186d5b02e8965cb557449253dd1f32facee0c71f3f771e351923f73f9ee8870a99472fa8a6b2aa8
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Andrew Kane, 2008-2014 Heroku (initial queries)
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# PgHero
|
2
|
+
|
3
|
+
:tada: Database insights made easy
|
4
|
+
|
5
|
+
Supports PostgreSQL 9.2+
|
6
|
+
|
7
|
+
For pure SQL, check out [PgHero.sql](https://github.com/ankane/pghero.sql)
|
8
|
+
|
9
|
+
:clap: Initial queries and inspiration by [Heroku](https://blog.heroku.com/archives/2013/5/10/more_insight_into_your_database_with_pgextras)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application’s Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'pghero'
|
17
|
+
```
|
18
|
+
|
19
|
+
And mount the dashboard in your router.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
mount PgHero::Engine, at: "pghero"
|
23
|
+
```
|
24
|
+
|
25
|
+
Be sure to [protect the dashboard](#security) in production.
|
26
|
+
|
27
|
+
## Insights
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
PgHero.running_queries
|
31
|
+
PgHero.long_running_queries
|
32
|
+
PgHero.index_usage
|
33
|
+
PgHero.missing_indexes
|
34
|
+
PgHero.unused_indexes
|
35
|
+
PgHero.relation_sizes
|
36
|
+
PgHero.index_hit_rate
|
37
|
+
PgHero.table_hit_rate
|
38
|
+
|
39
|
+
# kill queries
|
40
|
+
PgHero.kill(pid)
|
41
|
+
PgHero.kill_all
|
42
|
+
```
|
43
|
+
|
44
|
+
## Security
|
45
|
+
|
46
|
+
#### Basic Authentication
|
47
|
+
|
48
|
+
Set the following variables in your environment or an initializer.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
ENV["PGHERO_USERNAME"] = "andrew"
|
52
|
+
ENV["PGHERO_PASSWORD"] = "secret"
|
53
|
+
```
|
54
|
+
|
55
|
+
#### Devise
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
authenticate :user, lambda {|user| user.admin? } do
|
59
|
+
mount PgHero::Engine, at: "pghero"
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
## TODO
|
64
|
+
|
65
|
+
- demo
|
66
|
+
- better explanations on dashboard
|
67
|
+
- more space metrics
|
68
|
+
- poll for running queries
|
69
|
+
- better design (no bootstrap)
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
73
|
+
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
74
|
+
|
75
|
+
- [Report bugs](https://github.com/ankane/pghero/issues)
|
76
|
+
- Fix bugs and [submit pull requests](https://github.com/ankane/pghero/pulls)
|
77
|
+
- Write, clarify, or fix documentation
|
78
|
+
- Suggest or add new features
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module PgHero
|
2
|
+
class HomeController < ActionController::Base
|
3
|
+
layout "pg_hero/application"
|
4
|
+
|
5
|
+
protect_from_forgery
|
6
|
+
|
7
|
+
http_basic_authenticate_with name: ENV["PGHERO_USERNAME"], password: ENV["PGHERO_PASSWORD"] if ENV["PGHERO_PASSWORD"]
|
8
|
+
|
9
|
+
def index
|
10
|
+
@title = "Status"
|
11
|
+
@long_running_queries = PgHero.long_running_queries
|
12
|
+
@index_hit_rate = PgHero.index_hit_rate
|
13
|
+
@table_hit_rate = PgHero.table_hit_rate
|
14
|
+
@missing_indexes = PgHero.missing_indexes
|
15
|
+
@unused_indexes = PgHero.unused_indexes
|
16
|
+
@good_cache_rate = @table_hit_rate >= 0.99 && @index_hit_rate >= 0.99
|
17
|
+
end
|
18
|
+
|
19
|
+
def indexes
|
20
|
+
@title = "Indexes"
|
21
|
+
@index_usage = PgHero.index_usage
|
22
|
+
end
|
23
|
+
|
24
|
+
def space
|
25
|
+
@title = "Space"
|
26
|
+
@relation_sizes = PgHero.relation_sizes
|
27
|
+
end
|
28
|
+
|
29
|
+
def queries
|
30
|
+
@title = "Queries"
|
31
|
+
@running_queries = PgHero.running_queries
|
32
|
+
end
|
33
|
+
|
34
|
+
def kill
|
35
|
+
if PgHero.kill(params[:pid])
|
36
|
+
redirect_to root_path, notice: "Query killed"
|
37
|
+
else
|
38
|
+
redirect_to :back, notice: "Query no longer running"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def kill_all
|
43
|
+
PgHero.kill_all
|
44
|
+
redirect_to :back, notice: "Connections killed"
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= @title %> - PgHero</title>
|
5
|
+
|
6
|
+
<meta charset="utf-8" />
|
7
|
+
|
8
|
+
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
|
9
|
+
|
10
|
+
<style>
|
11
|
+
body {
|
12
|
+
padding-top: 20px;
|
13
|
+
}
|
14
|
+
|
15
|
+
h1 {
|
16
|
+
margin-top: 6px;
|
17
|
+
font-size: 20px;
|
18
|
+
}
|
19
|
+
|
20
|
+
h1, p {
|
21
|
+
margin-bottom: 20px;
|
22
|
+
}
|
23
|
+
|
24
|
+
.container {
|
25
|
+
max-width: 1000px;
|
26
|
+
}
|
27
|
+
|
28
|
+
#status {
|
29
|
+
margin-bottom: 20px;
|
30
|
+
}
|
31
|
+
|
32
|
+
#status div {
|
33
|
+
margin-bottom: 0;
|
34
|
+
border: none;
|
35
|
+
border-radius: 0;
|
36
|
+
}
|
37
|
+
|
38
|
+
.alert {
|
39
|
+
padding-top: 12px;
|
40
|
+
padding-bottom: 12px;
|
41
|
+
}
|
42
|
+
|
43
|
+
.queries > tbody > tr > td {
|
44
|
+
vertical-align: middle;
|
45
|
+
}
|
46
|
+
</style>
|
47
|
+
</head>
|
48
|
+
<body>
|
49
|
+
<div class="container">
|
50
|
+
<% if notice %>
|
51
|
+
<div class="alert alert-info"><%= notice %></div>
|
52
|
+
<% elsif Rails.env.development? %>
|
53
|
+
<div class="alert alert-info">Do not use development information to make decisions about your production environment</div>
|
54
|
+
<% end %>
|
55
|
+
|
56
|
+
<div class="row">
|
57
|
+
<div class="col-xs-3">
|
58
|
+
<ul class="nav nav-pills nav-stacked">
|
59
|
+
<!-- poor man's active_link_to -->
|
60
|
+
<li class="<%= controller.action_name == "index" ? "active" : "" %>"><%= link_to "Status", root_path %></li>
|
61
|
+
<li class="<%= controller.action_name == "indexes" ? "active" : "" %>"><%= link_to "Indexes", indexes_path %></li>
|
62
|
+
<li class="<%= controller.action_name == "space" ? "active" : "" %>"><%= link_to "Space", space_path %></li>
|
63
|
+
<li class="<%= controller.action_name == "queries" ? "active" : "" %>"><%= link_to "Queries", queries_path %></li>
|
64
|
+
</ul>
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<div class="col-xs-9">
|
68
|
+
<%= yield %>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
</body>
|
73
|
+
</html>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<table class="table queries">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th style="width: 10%;">Pid</th>
|
5
|
+
<th style="width: 10%;">State</th>
|
6
|
+
<th>Source</th>
|
7
|
+
<th style="width: 10%;">Waiting</th>
|
8
|
+
<th style="width: 15%;">Duration</th>
|
9
|
+
<th style="width: 10%;"></th>
|
10
|
+
</tr>
|
11
|
+
</thead>
|
12
|
+
<tbody>
|
13
|
+
<% queries.each do |query| %>
|
14
|
+
<tr>
|
15
|
+
<td><%= query["pid"] %></td>
|
16
|
+
<td><%= query["state"] %></td>
|
17
|
+
<td><%= query["source"] %></td>
|
18
|
+
<td><%= query["waiting"] == "t" ? "true" : "false" %></td>
|
19
|
+
<td><%= query["duration"] %></td>
|
20
|
+
<td class="text-right"><%= button_to "Kill", kill_path(pid: query["pid"]), class: "btn btn-info" %></td>
|
21
|
+
</tr>
|
22
|
+
<tr>
|
23
|
+
<td colspan="6" style="border-top: none; padding-top: 0;"><pre><%= query["query"] %></pre></td>
|
24
|
+
</tr>
|
25
|
+
<% end %>
|
26
|
+
</tbody>
|
27
|
+
</table>
|
@@ -0,0 +1,101 @@
|
|
1
|
+
<div id="status">
|
2
|
+
<div class="alert alert-<%= @long_running_queries.empty? ? "success" : "warning" %>">
|
3
|
+
<% if @long_running_queries.any? %>
|
4
|
+
<%= pluralize(@long_running_queries.size, "long running query") %>
|
5
|
+
<% else %>
|
6
|
+
No long running queries
|
7
|
+
<% end %>
|
8
|
+
</div>
|
9
|
+
<div class="alert alert-<%= @good_cache_rate ? "success" : "warning" %>">
|
10
|
+
<% if @good_cache_rate %>
|
11
|
+
Cache hit rate above 99%
|
12
|
+
<% else %>
|
13
|
+
Cache hit rate under 99%
|
14
|
+
<% end %>
|
15
|
+
</div>
|
16
|
+
<div class="alert alert-<%= @missing_indexes.empty? ? "success" : "warning" %>">
|
17
|
+
<% if @missing_indexes.any? %>
|
18
|
+
<%= pluralize(@missing_indexes.size, "possibly missing indexes") %>
|
19
|
+
<% else %>
|
20
|
+
No missing indexes
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
<div class="alert alert-<%= @unused_indexes.empty? ? "success" : "warning" %>">
|
24
|
+
<% if @unused_indexes.any? %>
|
25
|
+
<%= pluralize(@unused_indexes.size, "unused indexes") %>
|
26
|
+
<% else %>
|
27
|
+
No unused indexes
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<% if @long_running_queries.any? %>
|
33
|
+
<h1>Long Running Queries</h1>
|
34
|
+
|
35
|
+
<%= render partial: "queries_table", locals: {queries: @long_running_queries} %>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
<% if !@good_cache_rate %>
|
39
|
+
<h1>Low Cache Hit Rate</h1>
|
40
|
+
|
41
|
+
<p>
|
42
|
+
Index Hit Rate: <%= (@index_hit_rate * 100).round(1) %>%
|
43
|
+
<br />
|
44
|
+
Table Hit Rate: <%= (@table_hit_rate * 100).round(1) %>%
|
45
|
+
</p>
|
46
|
+
|
47
|
+
<p>
|
48
|
+
Try adding more memory.
|
49
|
+
<!-- TODO better suggestions -->
|
50
|
+
</p>
|
51
|
+
<% end %>
|
52
|
+
|
53
|
+
<% if @missing_indexes.any? %>
|
54
|
+
<h1>Missing Indexes</h1>
|
55
|
+
|
56
|
+
<p>These tables have a large number of rows and a lower % of time the index is used. Consider adding indexes where it makes sense.</p>
|
57
|
+
|
58
|
+
<table class="table">
|
59
|
+
<thead>
|
60
|
+
<tr>
|
61
|
+
<th>Table</th>
|
62
|
+
<th>% of Time Index Used</th>
|
63
|
+
<th>Rows in Table</th>
|
64
|
+
</tr>
|
65
|
+
</thead>
|
66
|
+
<tbody>
|
67
|
+
<% @missing_indexes.each do |query| %>
|
68
|
+
<tr>
|
69
|
+
<td><%= query["table"] %></td>
|
70
|
+
<td style="width: 30%;"><%= query["percent_of_times_index_used"] %></td>
|
71
|
+
<td style="width: 20%;"><%= query["rows_in_table"] %></td>
|
72
|
+
</tr>
|
73
|
+
<% end %>
|
74
|
+
</tbody>
|
75
|
+
</table>
|
76
|
+
<% end %>
|
77
|
+
|
78
|
+
<% if @unused_indexes.any? %>
|
79
|
+
<h1>Unused Indexes</h1>
|
80
|
+
|
81
|
+
<p>Unused indexes cause unnecessary overhead.</p>
|
82
|
+
|
83
|
+
<table class="table">
|
84
|
+
<thead>
|
85
|
+
<tr>
|
86
|
+
<th>Name</th>
|
87
|
+
<th>Index Size</th>
|
88
|
+
<th>Index Scans</th>
|
89
|
+
</tr>
|
90
|
+
</thead>
|
91
|
+
<tbody>
|
92
|
+
<% @unused_indexes.each do |query| %>
|
93
|
+
<tr>
|
94
|
+
<td><%= query["index"] %><div class="text-muted">on <%= query["table"] %></div></td>
|
95
|
+
<td style="width: 15%;"><%= query["index_size"] %></td>
|
96
|
+
<td style="width: 15%;"><%= query["index_scans"] %></td>
|
97
|
+
</tr>
|
98
|
+
<% end %>
|
99
|
+
</tbody>
|
100
|
+
</table>
|
101
|
+
<% end %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<h1>Index Usage</h1>
|
2
|
+
|
3
|
+
<table class="table">
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th>Table</th>
|
7
|
+
<th>% of Time Index Used</th>
|
8
|
+
<th>Rows in Table</th>
|
9
|
+
</tr>
|
10
|
+
</thead>
|
11
|
+
<tbody>
|
12
|
+
<% @index_usage.each do |query| %>
|
13
|
+
<tr>
|
14
|
+
<td><%= query["table"] %></td>
|
15
|
+
<td style="width: 30%;"><%= query["percent_of_times_index_used"] %></td>
|
16
|
+
<td style="width: 20%;"><%= query["rows_in_table"] %></td>
|
17
|
+
</tr>
|
18
|
+
<% end %>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<h1>Queries</h1>
|
2
|
+
|
3
|
+
<%= render partial: "queries_table", locals: {queries: @running_queries} %>
|
4
|
+
|
5
|
+
<p><%= button_to "Kill all connections", kill_all_path, class: "btn btn-danger" %></p>
|
6
|
+
|
7
|
+
<p class="text-muted">You may need to restart your Rails server afterwards.</p>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<h1>Largest Relations</h1>
|
2
|
+
|
3
|
+
<table class="table">
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th>Name</th>
|
7
|
+
<th style="width: 20%;">Type</th>
|
8
|
+
<th style="width: 20%;">Size</th>
|
9
|
+
</tr>
|
10
|
+
</thead>
|
11
|
+
<tbody>
|
12
|
+
<% @relation_sizes.each do |query| %>
|
13
|
+
<tr>
|
14
|
+
<td><%= query["name"] %></td>
|
15
|
+
<td><%= query["type"] %></td>
|
16
|
+
<td><%= query["size"] %></td>
|
17
|
+
</tr>
|
18
|
+
<% end %>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
data/config/routes.rb
ADDED
data/lib/pghero.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require "pghero/version"
|
2
|
+
require "pghero/engine" if defined?(Rails)
|
3
|
+
|
4
|
+
module PgHero
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def running_queries
|
8
|
+
execute %Q{
|
9
|
+
SELECT
|
10
|
+
pid,
|
11
|
+
state,
|
12
|
+
application_name AS source,
|
13
|
+
age(now(), xact_start) AS duration,
|
14
|
+
waiting,
|
15
|
+
query
|
16
|
+
FROM
|
17
|
+
pg_stat_activity
|
18
|
+
WHERE
|
19
|
+
query <> '<insufficient privilege>'
|
20
|
+
AND state <> 'idle'
|
21
|
+
AND pid <> pg_backend_pid()
|
22
|
+
ORDER BY
|
23
|
+
query_start DESC
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def long_running_queries
|
28
|
+
execute %Q{
|
29
|
+
SELECT
|
30
|
+
pid,
|
31
|
+
state,
|
32
|
+
application_name AS source,
|
33
|
+
age(now(), xact_start) AS duration,
|
34
|
+
waiting,
|
35
|
+
query
|
36
|
+
FROM
|
37
|
+
pg_stat_activity
|
38
|
+
WHERE
|
39
|
+
query <> '<insufficient privilege>'
|
40
|
+
AND state <> 'idle'
|
41
|
+
AND pid <> pg_backend_pid()
|
42
|
+
AND now() - query_start > interval '5 minutes'
|
43
|
+
ORDER BY
|
44
|
+
query_start DESC
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def index_hit_rate
|
49
|
+
execute(%Q{
|
50
|
+
SELECT
|
51
|
+
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS rate
|
52
|
+
FROM
|
53
|
+
pg_statio_user_indexes
|
54
|
+
}).first["rate"].to_f
|
55
|
+
end
|
56
|
+
|
57
|
+
def table_hit_rate
|
58
|
+
execute(%Q{
|
59
|
+
SELECT
|
60
|
+
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS rate
|
61
|
+
FROM
|
62
|
+
pg_statio_user_tables
|
63
|
+
}).first["rate"].to_f
|
64
|
+
end
|
65
|
+
|
66
|
+
def index_usage
|
67
|
+
execute %Q{
|
68
|
+
SELECT
|
69
|
+
relname AS table,
|
70
|
+
CASE idx_scan
|
71
|
+
WHEN 0 THEN 'Insufficient data'
|
72
|
+
ELSE (100 * idx_scan / (seq_scan + idx_scan))::text
|
73
|
+
END percent_of_times_index_used,
|
74
|
+
n_live_tup rows_in_table
|
75
|
+
FROM
|
76
|
+
pg_stat_user_tables
|
77
|
+
ORDER BY
|
78
|
+
n_live_tup DESC,
|
79
|
+
relname ASC
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def missing_indexes
|
84
|
+
execute %Q{
|
85
|
+
SELECT relname AS table,
|
86
|
+
CASE idx_scan
|
87
|
+
WHEN 0 THEN 'Insufficient data'
|
88
|
+
ELSE (100 * idx_scan / (seq_scan + idx_scan))::text
|
89
|
+
END percent_of_times_index_used,
|
90
|
+
n_live_tup rows_in_table
|
91
|
+
FROM
|
92
|
+
pg_stat_user_tables
|
93
|
+
WHERE
|
94
|
+
idx_scan < 95
|
95
|
+
AND n_live_tup >= 10000
|
96
|
+
ORDER BY
|
97
|
+
n_live_tup DESC,
|
98
|
+
relname ASC
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def unused_indexes
|
103
|
+
execute %Q{
|
104
|
+
SELECT
|
105
|
+
relname AS table,
|
106
|
+
indexrelname AS index,
|
107
|
+
pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
|
108
|
+
idx_scan as index_scans
|
109
|
+
FROM
|
110
|
+
pg_stat_user_indexes ui
|
111
|
+
INNER JOIN
|
112
|
+
pg_index i ON ui.indexrelid = i.indexrelid
|
113
|
+
WHERE
|
114
|
+
NOT indisunique
|
115
|
+
AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 8192
|
116
|
+
ORDER BY
|
117
|
+
pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
|
118
|
+
pg_relation_size(i.indexrelid) DESC,
|
119
|
+
relname ASC
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def relation_sizes
|
124
|
+
execute %Q{
|
125
|
+
SELECT
|
126
|
+
c.relname AS name,
|
127
|
+
CASE WHEN c.relkind = 'r' THEN 'table' ELSE 'index' END AS type,
|
128
|
+
pg_size_pretty(pg_table_size(c.oid)) AS size
|
129
|
+
FROM
|
130
|
+
pg_class c
|
131
|
+
LEFT JOIN
|
132
|
+
pg_namespace n ON (n.oid = c.relnamespace)
|
133
|
+
WHERE
|
134
|
+
n.nspname NOT IN ('pg_catalog', 'information_schema')
|
135
|
+
AND n.nspname !~ '^pg_toast'
|
136
|
+
AND c.relkind IN ('r', 'i')
|
137
|
+
ORDER BY
|
138
|
+
pg_table_size(c.oid) DESC,
|
139
|
+
name ASC
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
def kill(pid)
|
144
|
+
ActiveRecord::Base.connection.execute("SELECT pg_cancel_backend(#{pid.to_i})").first["pg_cancel_backend"] == "t"
|
145
|
+
end
|
146
|
+
|
147
|
+
def kill_all
|
148
|
+
execute %Q{
|
149
|
+
SELECT
|
150
|
+
pg_terminate_backend(pid)
|
151
|
+
FROM
|
152
|
+
pg_stat_activity
|
153
|
+
WHERE
|
154
|
+
pid <> pg_backend_pid()
|
155
|
+
AND query <> '<insufficient privilege>'
|
156
|
+
}
|
157
|
+
true
|
158
|
+
end
|
159
|
+
|
160
|
+
def execute(sql)
|
161
|
+
# squish for logs
|
162
|
+
ActiveRecord::Base.connection.select_all(sql.squish).to_a
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
data/pghero.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pghero/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "pghero"
|
8
|
+
spec.version = PgHero::VERSION
|
9
|
+
spec.authors = ["Andrew Kane"]
|
10
|
+
spec.email = ["andrew@chartkick.com"]
|
11
|
+
spec.summary = %q{Database insights made easy}
|
12
|
+
spec.description = %q{Database insights made easy}
|
13
|
+
spec.homepage = "https://github.com/ankane/pghero"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pghero
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Kane
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Database insights made easy
|
42
|
+
email:
|
43
|
+
- andrew@chartkick.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- app/controllers/pg_hero/home_controller.rb
|
54
|
+
- app/views/layouts/pg_hero/application.html.erb
|
55
|
+
- app/views/pg_hero/home/_queries_table.html.erb
|
56
|
+
- app/views/pg_hero/home/index.html.erb
|
57
|
+
- app/views/pg_hero/home/indexes.html.erb
|
58
|
+
- app/views/pg_hero/home/queries.html.erb
|
59
|
+
- app/views/pg_hero/home/space.html.erb
|
60
|
+
- config/routes.rb
|
61
|
+
- lib/pghero.rb
|
62
|
+
- lib/pghero/engine.rb
|
63
|
+
- lib/pghero/version.rb
|
64
|
+
- pghero.gemspec
|
65
|
+
homepage: https://github.com/ankane/pghero
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.2.2
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Database insights made easy
|
89
|
+
test_files: []
|
90
|
+
has_rdoc:
|