pghero 1.5.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +75 -0
- data/Gemfile +1 -1
- data/README.md +8 -14
- data/app/assets/javascripts/pghero/Chart.bundle.js +7512 -5661
- data/app/assets/javascripts/pghero/application.js +9 -0
- data/app/assets/javascripts/pghero/chartkick.js +511 -152
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
- data/app/assets/stylesheets/pghero/application.css +66 -2
- data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
- data/app/controllers/pg_hero/home_controller.rb +166 -63
- data/app/helpers/pg_hero/base_helper.rb +15 -0
- data/app/views/layouts/pg_hero/application.html.erb +4 -3
- data/app/views/pg_hero/home/_connections_table.html.erb +2 -9
- data/app/views/pg_hero/home/_live_queries_table.html.erb +11 -8
- data/app/views/pg_hero/home/_queries_table.html.erb +21 -10
- data/app/views/pg_hero/home/_suggested_index.html.erb +1 -1
- data/app/views/pg_hero/home/connections.html.erb +19 -1
- data/app/views/pg_hero/home/explain.html.erb +1 -1
- data/app/views/pg_hero/home/index.html.erb +125 -66
- data/app/views/pg_hero/home/index_bloat.html.erb +69 -0
- data/app/views/pg_hero/home/live_queries.html.erb +3 -1
- data/app/views/pg_hero/home/maintenance.html.erb +11 -7
- data/app/views/pg_hero/home/queries.html.erb +10 -0
- data/app/views/pg_hero/home/relation_space.html.erb +9 -0
- data/app/views/pg_hero/home/show_query.html.erb +107 -0
- data/app/views/pg_hero/home/space.html.erb +64 -10
- data/app/views/pg_hero/home/system.html.erb +16 -4
- data/config/routes.rb +4 -2
- data/guides/Linux.md +20 -0
- data/guides/Rails.md +30 -4
- data/guides/Suggested-Indexes.md +1 -1
- data/lib/generators/pghero/config_generator.rb +13 -0
- data/lib/generators/pghero/query_stats_generator.rb +7 -1
- data/lib/generators/pghero/space_stats_generator.rb +7 -1
- data/lib/generators/pghero/templates/config.yml +23 -0
- data/lib/generators/pghero/templates/query_stats.rb +1 -1
- data/lib/generators/pghero/templates/space_stats.rb +1 -1
- data/lib/pghero/database.rb +6 -2
- data/lib/pghero/engine.rb +10 -7
- data/lib/pghero/methods/basic.rb +83 -11
- data/lib/pghero/methods/connections.rb +16 -56
- data/lib/pghero/methods/explain.rb +3 -5
- data/lib/pghero/methods/indexes.rb +179 -21
- data/lib/pghero/methods/kill.rb +2 -2
- data/lib/pghero/methods/maintenance.rb +34 -20
- data/lib/pghero/methods/queries.rb +28 -45
- data/lib/pghero/methods/query_stats.rb +97 -91
- data/lib/pghero/methods/{replica.rb → replication.rb} +20 -4
- data/lib/pghero/methods/sequences.rb +4 -5
- data/lib/pghero/methods/space.rb +101 -8
- data/lib/pghero/methods/suggested_indexes.rb +49 -108
- data/lib/pghero/methods/system.rb +23 -13
- data/lib/pghero/methods/tables.rb +9 -9
- data/lib/pghero/methods/users.rb +10 -12
- data/lib/pghero/version.rb +1 -1
- data/lib/pghero.rb +34 -36
- data/lib/tasks/pghero.rake +5 -0
- data/test/basic_test.rb +38 -0
- data/test/best_index_test.rb +3 -3
- data/test/suggested_indexes_test.rb +0 -2
- data/test/test_helper.rb +38 -40
- metadata +14 -7
- data/app/views/pg_hero/home/index_usage.html.erb +0 -27
- data/test/explain_test.rb +0 -18
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<div class="content">
|
|
2
|
+
<pre><code style="max-height: 230px; overflow: hidden;" onclick="this.style.maxHeight = 'none';"><%= @query %></code></pre>
|
|
3
|
+
<script>
|
|
4
|
+
highlightQueries()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<% if @explainable_query %>
|
|
8
|
+
<p>
|
|
9
|
+
<% button_path, button_options = Rails.version >= "4.1" ? [explain_path, {params: {query: @explainable_query}}] : [explain_path(query: @explainable_query), {}] %>
|
|
10
|
+
<%= button_to "Explain", button_path, button_options.merge(form: {target: "_blank"}, class: "btn btn-info") %>
|
|
11
|
+
</p>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<% if @origins && @origins.keys.select { |k| k.length > 0 }.any? %>
|
|
15
|
+
<table style="table-layout: auto;">
|
|
16
|
+
<thead>
|
|
17
|
+
<tr>
|
|
18
|
+
<th colspan="2">
|
|
19
|
+
<div style="float: right;">Approx. Time</div>
|
|
20
|
+
Origin
|
|
21
|
+
</th>
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
<tbody>
|
|
25
|
+
<% @origins.sort_by { |o, c| [-c, o.to_s] }.each do |origin, count| %>
|
|
26
|
+
<tr>
|
|
27
|
+
<td class="origin" style="width: 90%;">
|
|
28
|
+
<% if origin.length > 0 %>
|
|
29
|
+
<%= origin %>
|
|
30
|
+
<% else %>
|
|
31
|
+
<span class="text-muted">Unknown</span>
|
|
32
|
+
<% end %>
|
|
33
|
+
</td>
|
|
34
|
+
<td style="text-align: right; width: 10%;">
|
|
35
|
+
<% pct = (100.0 * count / @total_count).round %>
|
|
36
|
+
<% if pct == 0 %>
|
|
37
|
+
< 1%
|
|
38
|
+
<% else %>
|
|
39
|
+
<%= pct %>%
|
|
40
|
+
<% end %>
|
|
41
|
+
</td>
|
|
42
|
+
</tr>
|
|
43
|
+
<% end %>
|
|
44
|
+
</tbody>
|
|
45
|
+
</table>
|
|
46
|
+
<% end %>
|
|
47
|
+
|
|
48
|
+
<!-- chart -->
|
|
49
|
+
<% if @chart_data %>
|
|
50
|
+
<h1>Total Time <small>ms</small></h1>
|
|
51
|
+
<div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
52
|
+
<script>
|
|
53
|
+
new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<h1>Average Time <small>ms</small></h1>
|
|
57
|
+
<div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
58
|
+
<script>
|
|
59
|
+
new Chartkick.LineChart("chart-2", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<h1>Calls</h1>
|
|
63
|
+
<div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
64
|
+
<script>
|
|
65
|
+
new Chartkick.LineChart("chart-3", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
|
|
66
|
+
</script>
|
|
67
|
+
<% else %>
|
|
68
|
+
<p>
|
|
69
|
+
Enable
|
|
70
|
+
<%= link_to "historical query stats", "https://github.com/ankane/pghero", target: "_blank" %>
|
|
71
|
+
to see more details
|
|
72
|
+
</p>
|
|
73
|
+
<% end %>
|
|
74
|
+
|
|
75
|
+
<!-- table info -->
|
|
76
|
+
<% if @tables.any? %>
|
|
77
|
+
<h1>Tables</h1>
|
|
78
|
+
<table>
|
|
79
|
+
<thead>
|
|
80
|
+
<tr>
|
|
81
|
+
<th style="width: 25%;">Name</th>
|
|
82
|
+
<th style="width: 25%;">Rows</th>
|
|
83
|
+
<th>Indexes</th>
|
|
84
|
+
</tr>
|
|
85
|
+
</thead>
|
|
86
|
+
<tbody>
|
|
87
|
+
<% @tables.each do |table| %>
|
|
88
|
+
<tr>
|
|
89
|
+
<td><%= table %></td>
|
|
90
|
+
<td><%= @row_counts[table] %></td>
|
|
91
|
+
<td>
|
|
92
|
+
<ul class="list-normal">
|
|
93
|
+
<% @indexes_by_table[table].to_a.sort_by { |i| [i[:primary] ? 0 : 1, i[:columns]] }.each do |i3| %>
|
|
94
|
+
<li>
|
|
95
|
+
<%= i3[:columns].join(", ") %><% if i3[:using] != "btree" %>
|
|
96
|
+
<%= i3[:using].to_s.upcase %><% end %>
|
|
97
|
+
<% if i3[:primary] %> PRIMARY<% elsif i3[:unique] %> UNIQUE<% end %>
|
|
98
|
+
</li>
|
|
99
|
+
<% end %>
|
|
100
|
+
</ul>
|
|
101
|
+
</td>
|
|
102
|
+
</tr>
|
|
103
|
+
<% end %>
|
|
104
|
+
</tbody>
|
|
105
|
+
</table>
|
|
106
|
+
<% end %>
|
|
107
|
+
</div>
|
|
@@ -3,25 +3,79 @@
|
|
|
3
3
|
|
|
4
4
|
<p>Database Size: <%= @database_size %></p>
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
<% if @system_stats_enabled %>
|
|
7
|
+
<div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
8
|
+
<script>
|
|
9
|
+
new Chartkick.LineChart("chart-1", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"]})
|
|
10
|
+
</script>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<!--
|
|
14
|
+
<% if @index_bloat.any? %>
|
|
15
|
+
<p>Check out <%= link_to "index bloat", index_bloat_path %> for an easy way to reclaim space.</p>
|
|
16
|
+
<% end %>
|
|
17
|
+
-->
|
|
18
|
+
|
|
19
|
+
<% if @unused_indexes.any? %>
|
|
20
|
+
<p>
|
|
21
|
+
<%= pluralize(@unused_indexes.size, "unused index") %>. Remove them
|
|
22
|
+
<% if @show_migrations %>
|
|
23
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration').style.display = 'block';">with a migration</a>
|
|
24
|
+
<% end %>
|
|
25
|
+
for faster writes.
|
|
26
|
+
|
|
27
|
+
<% if @database.replicating? %>
|
|
28
|
+
Check they aren’t used on replicas.
|
|
29
|
+
<% end %>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<div id="migration" style="display: none;">
|
|
33
|
+
<pre>rails g migration remove_unused_indexes</pre>
|
|
34
|
+
<p>And paste</p>
|
|
35
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.sort_by { |q| q[:index] }.each do |query| %>
|
|
36
|
+
remove_index <%= query[:table].to_sym.inspect %>, name: <%= query[:index].to_s.inspect %><% end %></pre>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
|
|
40
|
+
<table class="table space-table">
|
|
7
41
|
<thead>
|
|
8
42
|
<tr>
|
|
9
|
-
<th
|
|
10
|
-
<th style="width: 15%;"
|
|
11
|
-
|
|
43
|
+
<th><%= link_to "Relation", {sort: "name"} %></th>
|
|
44
|
+
<th style="width: 15%;"><%= link_to "Size", {} %></th>
|
|
45
|
+
<% if @space_stats_enabled %>
|
|
46
|
+
<th style="width: 15%;"><%= link_to "#{@days}d Growth", {sort: "growth"} %></th>
|
|
47
|
+
<% end %>
|
|
12
48
|
</tr>
|
|
13
49
|
</thead>
|
|
14
50
|
<tbody>
|
|
15
51
|
<% @relation_sizes.each do |query| %>
|
|
16
52
|
<tr>
|
|
17
|
-
<td>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
53
|
+
<td style="<%= query[:type] == "index" ? "font-style: italic;" : "" %>">
|
|
54
|
+
<span style="word-break: break-all;">
|
|
55
|
+
<% name = query[:relation] || query[:table] %>
|
|
56
|
+
<% if @space_stats_enabled %>
|
|
57
|
+
<%= link_to name, relation_space_path(name), target: "_blank", style: "color: inherit;" %>
|
|
58
|
+
<% else %>
|
|
59
|
+
<%= name %>
|
|
60
|
+
<% end %>
|
|
61
|
+
</span>
|
|
62
|
+
<% if query[:schema] != "public" %>
|
|
63
|
+
<span class="text-muted"><%= query[:schema] %></span>
|
|
64
|
+
<% end %>
|
|
65
|
+
<% if @unused_index_names.include?(query[:name]) %>
|
|
66
|
+
<span class="unused-index">UNUSED</span>
|
|
21
67
|
<% end %>
|
|
22
68
|
</td>
|
|
23
|
-
<td
|
|
24
|
-
|
|
69
|
+
<td><%= query[:size] %></td>
|
|
70
|
+
<% if @space_stats_enabled %>
|
|
71
|
+
<td>
|
|
72
|
+
<% if @growth_bytes_by_relation[query[:relation]] %>
|
|
73
|
+
<% if @growth_bytes_by_relation[query[:relation]] < 0 %>-<% end %><%= PgHero.pretty_size(@growth_bytes_by_relation[query[:relation]].abs) %>
|
|
74
|
+
<% else %>
|
|
75
|
+
<span class="text-muted">Unknown</span>
|
|
76
|
+
<% end %>
|
|
77
|
+
</td>
|
|
78
|
+
<% end %>
|
|
25
79
|
</tr>
|
|
26
80
|
<% end %>
|
|
27
81
|
</tbody>
|
|
@@ -7,16 +7,28 @@
|
|
|
7
7
|
<% path_options = {duration: params[:duration], period: params[:period]} %>
|
|
8
8
|
|
|
9
9
|
<h1>CPU</h1>
|
|
10
|
-
<div style="margin-bottom: 20px;"
|
|
10
|
+
<div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
11
|
+
<script>
|
|
12
|
+
new Chartkick.LineChart("chart-1", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"]})
|
|
13
|
+
</script>
|
|
11
14
|
|
|
12
15
|
<h1>Load</h1>
|
|
13
|
-
<div style="margin-bottom: 20px;"
|
|
16
|
+
<div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
17
|
+
<script>
|
|
18
|
+
new Chartkick.LineChart("chart-2", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"]})
|
|
19
|
+
</script>
|
|
14
20
|
|
|
15
21
|
<h1>Connections</h1>
|
|
16
|
-
<div style="margin-bottom: 20px;"
|
|
22
|
+
<div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
23
|
+
<script>
|
|
24
|
+
new Chartkick.LineChart("chart-3", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"]})
|
|
25
|
+
</script>
|
|
17
26
|
|
|
18
27
|
<% if @database.replica? %>
|
|
19
28
|
<h1>Replication Lag</h1>
|
|
20
|
-
<div style="margin-bottom: 20px;"
|
|
29
|
+
<div id="chart-4" class="chart" style="margin-bottom: 20px;">Loading...</div>
|
|
30
|
+
<script>
|
|
31
|
+
new Chartkick.LineChart("chart-4", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"]})
|
|
32
|
+
</script>
|
|
21
33
|
<% end %>
|
|
22
34
|
</div>
|
data/config/routes.rb
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
PgHero::Engine.routes.draw do
|
|
2
2
|
scope "(:database)", constraints: proc { |req| (PgHero.config["databases"].keys + [nil]).include?(req.params[:database]) } do
|
|
3
|
-
get "index_usage", to: "home#index_usage"
|
|
4
3
|
get "space", to: "home#space"
|
|
4
|
+
get "space/:relation", to: "home#relation_space", as: :relation_space
|
|
5
|
+
get "index_bloat", to: "home#index_bloat"
|
|
5
6
|
get "live_queries", to: "home#live_queries"
|
|
6
7
|
get "queries", to: "home#queries"
|
|
8
|
+
get "queries/:query_hash", to: "home#show_query", as: :show_query
|
|
7
9
|
get "system", to: "home#system"
|
|
8
10
|
get "cpu_usage", to: "home#cpu_usage"
|
|
9
11
|
get "connection_stats", to: "home#connection_stats"
|
|
10
12
|
get "replication_lag_stats", to: "home#replication_lag_stats"
|
|
11
13
|
get "load_stats", to: "home#load_stats"
|
|
14
|
+
get "free_space_stats", to: "home#free_space_stats"
|
|
12
15
|
get "explain", to: "home#explain"
|
|
13
16
|
get "tune", to: "home#tune"
|
|
14
17
|
get "connections", to: "home#connections"
|
|
@@ -23,7 +26,6 @@ PgHero::Engine.routes.draw do
|
|
|
23
26
|
# legacy routes
|
|
24
27
|
get "system_stats" => redirect("system")
|
|
25
28
|
get "query_stats" => redirect("queries")
|
|
26
|
-
get "indexes" => redirect("index_usage")
|
|
27
29
|
|
|
28
30
|
root to: "home#index"
|
|
29
31
|
end
|
data/guides/Linux.md
CHANGED
|
@@ -186,6 +186,26 @@ sudo pghero config:set PGHERO_SECRET_ACCESS_KEY=secret123
|
|
|
186
186
|
sudo pghero config:set PGHERO_DB_INSTANCE_IDENTIFIER=epona
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
## Multiple Databases
|
|
190
|
+
|
|
191
|
+
Create a `pghero.yml` with:
|
|
192
|
+
|
|
193
|
+
```yml
|
|
194
|
+
production:
|
|
195
|
+
databases:
|
|
196
|
+
primary:
|
|
197
|
+
url: postgres://...
|
|
198
|
+
replica:
|
|
199
|
+
url: postgres://...
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
And run:
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
cat pghero.yml | sudo pghero run sh -c "cat > config/pghero.yml"
|
|
206
|
+
sudo service pghero restart
|
|
207
|
+
```
|
|
208
|
+
|
|
189
209
|
## Customize
|
|
190
210
|
|
|
191
211
|
Minimum time for long running queries
|
data/guides/Rails.md
CHANGED
|
@@ -19,7 +19,7 @@ Be sure to [secure the dashboard](#security) in production.
|
|
|
19
19
|
PgHero can suggest indexes to add. To enable, add to your Gemfile:
|
|
20
20
|
|
|
21
21
|
```ruby
|
|
22
|
-
gem 'pg_query'
|
|
22
|
+
gem 'pg_query', '>= 0.9.0'
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
and make sure [query stats](#query-stats) are enabled. Read about how it works [here](Suggested-Indexes.md).
|
|
@@ -38,7 +38,7 @@ ENV["PGHERO_PASSWORD"] = "hyrule"
|
|
|
38
38
|
#### Devise
|
|
39
39
|
|
|
40
40
|
```ruby
|
|
41
|
-
authenticate :user,
|
|
41
|
+
authenticate :user, -> (user) { user.admin? } do
|
|
42
42
|
mount PgHero::Engine, at: "pghero"
|
|
43
43
|
end
|
|
44
44
|
```
|
|
@@ -78,11 +78,10 @@ ENV["PGHERO_STATS_DATABASE_URL"]
|
|
|
78
78
|
|
|
79
79
|
## System Stats
|
|
80
80
|
|
|
81
|
-
CPU usage
|
|
81
|
+
CPU usage, IOPS, and other stats are available for Amazon RDS. Add these lines to your application’s Gemfile:
|
|
82
82
|
|
|
83
83
|
```ruby
|
|
84
84
|
gem 'aws-sdk'
|
|
85
|
-
gem 'chartkick'
|
|
86
85
|
```
|
|
87
86
|
|
|
88
87
|
And add these variables to your environment:
|
|
@@ -242,6 +241,33 @@ PgHero.drop_user("ganondorf")
|
|
|
242
241
|
|
|
243
242
|
## Upgrading
|
|
244
243
|
|
|
244
|
+
### 2.0.0
|
|
245
|
+
|
|
246
|
+
New features
|
|
247
|
+
|
|
248
|
+
- Query details page
|
|
249
|
+
|
|
250
|
+
Breaking changes
|
|
251
|
+
|
|
252
|
+
- Methods now return symbols for keys instead of strings
|
|
253
|
+
- Methods raise `PgHero::NotEnabled` error when a feature isn’t enabled
|
|
254
|
+
- Requires pg_query 0.9.0+ for suggested indexes
|
|
255
|
+
- Historical query stats require the `pghero_query_stats` table to have `query_hash` and `user` columns
|
|
256
|
+
- Removed `with` option - use:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
PgHero.databases[:database2].running_queries
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
instead of
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
PgHero.with(:database2) { PgHero.running_queries }
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- Removed options from `connection_sources` method
|
|
269
|
+
- Removed `locks` method
|
|
270
|
+
|
|
245
271
|
### 1.5.0
|
|
246
272
|
|
|
247
273
|
For query stats grouping by user, create a migration with:
|
data/guides/Suggested-Indexes.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# How PgHero Suggests Indexes
|
|
2
2
|
|
|
3
|
-
1. Get the most time-consuming queries from [pg_stat_statements](http://www.postgresql.org/docs/
|
|
3
|
+
1. Get the most time-consuming queries from [pg_stat_statements](http://www.postgresql.org/docs/current/static/pgstatstatements.html).
|
|
4
4
|
|
|
5
5
|
2. Parse queries with [pg_query](https://github.com/lfittl/pg_query). Look for a single table with a `WHERE` clause that consists of only `=`, `IN`, `IS NULL` or `IS NOT NULL` and/or an `ORDER BY` clause.
|
|
6
6
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
|
|
3
|
+
module Pghero
|
|
4
|
+
module Generators
|
|
5
|
+
class ConfigGenerator < Rails::Generators::Base
|
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
7
|
+
|
|
8
|
+
def create_initializer
|
|
9
|
+
template "config.yml", "config/pghero.yml"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -22,7 +22,13 @@ module Pghero
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def copy_migration
|
|
25
|
-
migration_template "query_stats.rb", "db/migrate/create_pghero_query_stats.rb"
|
|
25
|
+
migration_template "query_stats.rb", "db/migrate/create_pghero_query_stats.rb", migration_version: migration_version
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def migration_version
|
|
29
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
|
30
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
31
|
+
end
|
|
26
32
|
end
|
|
27
33
|
end
|
|
28
34
|
end
|
|
@@ -22,7 +22,13 @@ module Pghero
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def copy_migration
|
|
25
|
-
migration_template "space_stats.rb", "db/migrate/create_pghero_space_stats.rb"
|
|
25
|
+
migration_template "space_stats.rb", "db/migrate/create_pghero_space_stats.rb", migration_version: migration_version
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def migration_version
|
|
29
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
|
30
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
31
|
+
end
|
|
26
32
|
end
|
|
27
33
|
end
|
|
28
34
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
databases:
|
|
2
|
+
main:
|
|
3
|
+
# Database URL (defaults to app database)
|
|
4
|
+
# url: <%= ENV["DATABASE_URL"] %>
|
|
5
|
+
|
|
6
|
+
# Minimum time for long running queries
|
|
7
|
+
# long_running_query_sec: 60
|
|
8
|
+
|
|
9
|
+
# Minimum average time for slow queries
|
|
10
|
+
# slow_query_ms: 20
|
|
11
|
+
|
|
12
|
+
# Minimum calls for slow queries
|
|
13
|
+
# slow_query_calls: 100
|
|
14
|
+
|
|
15
|
+
# Minimum connections for high connections warning
|
|
16
|
+
# total_connections_threshold: 100
|
|
17
|
+
|
|
18
|
+
# Add more databases
|
|
19
|
+
# other:
|
|
20
|
+
# url: <%= ENV["OTHER_DATABASE_URL"] %>
|
|
21
|
+
|
|
22
|
+
# Time zone (defaults to app time zone)
|
|
23
|
+
# time_zone: "Pacific Time (US & Canada)"
|
data/lib/pghero/database.rb
CHANGED
|
@@ -8,7 +8,7 @@ module PgHero
|
|
|
8
8
|
include Methods::Maintenance
|
|
9
9
|
include Methods::Queries
|
|
10
10
|
include Methods::QueryStats
|
|
11
|
-
include Methods::
|
|
11
|
+
include Methods::Replication
|
|
12
12
|
include Methods::Sequences
|
|
13
13
|
include Methods::Space
|
|
14
14
|
include Methods::SuggestedIndexes
|
|
@@ -20,7 +20,7 @@ module PgHero
|
|
|
20
20
|
|
|
21
21
|
def initialize(id, config)
|
|
22
22
|
@id = id
|
|
23
|
-
@config = config
|
|
23
|
+
@config = config || {}
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def name
|
|
@@ -55,6 +55,10 @@ module PgHero
|
|
|
55
55
|
(config["long_running_query_sec"] || PgHero.config["long_running_query_sec"] || PgHero.long_running_query_sec).to_i
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
def index_bloat_bytes
|
|
59
|
+
(config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] || 100.megabytes).to_i
|
|
60
|
+
end
|
|
61
|
+
|
|
58
62
|
private
|
|
59
63
|
|
|
60
64
|
def connection_model
|
data/lib/pghero/engine.rb
CHANGED
|
@@ -3,13 +3,16 @@ module PgHero
|
|
|
3
3
|
isolate_namespace PgHero
|
|
4
4
|
|
|
5
5
|
initializer "pghero", group: :all do |app|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
# check if Rails api mode
|
|
7
|
+
if app.config.respond_to?(:assets)
|
|
8
|
+
if defined?(Sprockets) && Sprockets::VERSION >= "4"
|
|
9
|
+
app.config.assets.precompile << "pghero/application.js"
|
|
10
|
+
app.config.assets.precompile << "pghero/application.css"
|
|
11
|
+
else
|
|
12
|
+
# use a proc instead of a string
|
|
13
|
+
app.config.assets.precompile << proc { |path| path == "pghero/application.js" }
|
|
14
|
+
app.config.assets.precompile << proc { |path| path == "pghero/application.css" }
|
|
15
|
+
end
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
PgHero.time_zone = PgHero.config["time_zone"] if PgHero.config["time_zone"]
|
data/lib/pghero/methods/basic.rb
CHANGED
|
@@ -4,44 +4,78 @@ module PgHero
|
|
|
4
4
|
def settings
|
|
5
5
|
names =
|
|
6
6
|
if server_version_num >= 90500
|
|
7
|
-
%
|
|
7
|
+
%i(
|
|
8
8
|
max_connections shared_buffers effective_cache_size work_mem
|
|
9
9
|
maintenance_work_mem min_wal_size max_wal_size checkpoint_completion_target
|
|
10
10
|
wal_buffers default_statistics_target
|
|
11
11
|
)
|
|
12
12
|
else
|
|
13
|
-
%
|
|
13
|
+
%i(
|
|
14
14
|
max_connections shared_buffers effective_cache_size work_mem
|
|
15
15
|
maintenance_work_mem checkpoint_segments checkpoint_completion_target
|
|
16
16
|
wal_buffers default_statistics_target
|
|
17
17
|
)
|
|
18
18
|
end
|
|
19
|
-
Hash[names.map { |name| [name,
|
|
19
|
+
Hash[names.map { |name| [name, select_one("SHOW #{name}")] }]
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def ssl_used?
|
|
23
23
|
ssl_used = nil
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
with_transaction(rollback: true) do
|
|
25
|
+
begin
|
|
26
|
+
execute("CREATE EXTENSION IF NOT EXISTS sslinfo")
|
|
27
|
+
rescue ActiveRecord::StatementInvalid
|
|
28
|
+
# not superuser
|
|
29
|
+
end
|
|
30
|
+
ssl_used = select_one("SELECT ssl_is_used()")
|
|
28
31
|
end
|
|
29
32
|
ssl_used
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
def database_name
|
|
33
|
-
|
|
36
|
+
select_one("SELECT current_database()")
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def server_version
|
|
37
|
-
|
|
40
|
+
@server_version ||= select_one("SHOW server_version")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def server_version_num
|
|
44
|
+
@server_version_num ||= select_one("SHOW server_version_num").to_i
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def quote_ident(value)
|
|
48
|
+
quote_table_name(value)
|
|
38
49
|
end
|
|
39
50
|
|
|
40
51
|
private
|
|
41
52
|
|
|
42
|
-
def select_all(sql)
|
|
53
|
+
def select_all(sql, conn = nil)
|
|
54
|
+
conn ||= connection
|
|
43
55
|
# squish for logs
|
|
44
|
-
|
|
56
|
+
result = conn.select_all(squish(sql))
|
|
57
|
+
cast_method = ActiveRecord::VERSION::MAJOR < 5 ? :type_cast : :cast_value
|
|
58
|
+
result.map { |row| Hash[row.map { |col, val| [col.to_sym, result.column_types[col].send(cast_method, val)] }] }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def select_all_stats(sql)
|
|
62
|
+
select_all(sql, stats_connection)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def select_all_size(sql)
|
|
66
|
+
result = select_all(sql)
|
|
67
|
+
result.each do |row|
|
|
68
|
+
row[:size] = PgHero.pretty_size(row[:size_bytes])
|
|
69
|
+
end
|
|
70
|
+
result
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def select_one(sql, conn = nil)
|
|
74
|
+
select_all(sql, conn).first.values.first
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def select_one_stats(sql)
|
|
78
|
+
select_one(sql, stats_connection)
|
|
45
79
|
end
|
|
46
80
|
|
|
47
81
|
def execute(sql)
|
|
@@ -52,6 +86,16 @@ module PgHero
|
|
|
52
86
|
connection_model.connection
|
|
53
87
|
end
|
|
54
88
|
|
|
89
|
+
def stats_connection
|
|
90
|
+
::PgHero::QueryStats.connection
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def insert_stats(table, columns, values)
|
|
94
|
+
values = values.map { |v| "(#{v.map { |v2| quote(v2) }.join(",")})" }.join(",")
|
|
95
|
+
columns = columns.map { |v| quote_table_name(v) }.join(",")
|
|
96
|
+
stats_connection.execute("INSERT INTO #{quote_table_name(table)} (#{columns}) VALUES #{values}")
|
|
97
|
+
end
|
|
98
|
+
|
|
55
99
|
# from ActiveSupport
|
|
56
100
|
def squish(str)
|
|
57
101
|
str.to_s.gsub(/\A[[:space:]]+/, "").gsub(/[[:space:]]+\z/, "").gsub(/[[:space:]]+/, " ")
|
|
@@ -72,6 +116,34 @@ module PgHero
|
|
|
72
116
|
part
|
|
73
117
|
end
|
|
74
118
|
end
|
|
119
|
+
|
|
120
|
+
def with_transaction(lock_timeout: nil, statement_timeout: nil, rollback: false)
|
|
121
|
+
connection_model.transaction do
|
|
122
|
+
select_all "SET LOCAL statement_timeout = #{statement_timeout.to_i}" if statement_timeout
|
|
123
|
+
select_all "SET LOCAL lock_timeout = #{lock_timeout.to_i}" if lock_timeout
|
|
124
|
+
yield
|
|
125
|
+
raise ActiveRecord::Rollback if rollback
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def table_exists?(table)
|
|
130
|
+
["PostgreSQL", "PostGIS"].include?(stats_connection.adapter_name) &&
|
|
131
|
+
select_one_stats(<<-SQL
|
|
132
|
+
SELECT EXISTS (
|
|
133
|
+
SELECT
|
|
134
|
+
1
|
|
135
|
+
FROM
|
|
136
|
+
pg_catalog.pg_class c
|
|
137
|
+
INNER JOIN
|
|
138
|
+
pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
139
|
+
WHERE
|
|
140
|
+
n.nspname = 'public'
|
|
141
|
+
AND c.relname = #{quote(table)}
|
|
142
|
+
AND c.relkind = 'r'
|
|
143
|
+
)
|
|
144
|
+
SQL
|
|
145
|
+
)
|
|
146
|
+
end
|
|
75
147
|
end
|
|
76
148
|
end
|
|
77
149
|
end
|