pghero 2.2.0 → 2.2.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/CHANGELOG.md +6 -0
- data/LICENSE.txt +1 -1
- data/app/assets/stylesheets/pghero/application.css +24 -14
- data/app/controllers/pg_hero/home_controller.rb +4 -4
- data/app/helpers/pg_hero/{base_helper.rb → home_helper.rb} +5 -1
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +6 -6
- data/app/views/pg_hero/home/explain.html.erb +2 -2
- data/app/views/pg_hero/home/show_query.html.erb +1 -1
- data/lib/generators/pghero/templates/config.yml.tt +13 -13
- data/lib/pghero.rb +13 -8
- data/lib/pghero/database.rb +2 -2
- data/lib/pghero/methods/basic.rb +7 -3
- data/lib/pghero/methods/kill.rb +2 -2
- data/lib/pghero/methods/replication.rb +12 -4
- data/lib/pghero/methods/sequences.rb +3 -0
- data/lib/pghero/version.rb +1 -1
- metadata +4 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7edafa3113ab1d1e6dd6df9c2abb1fe90ccd1077811475c001cfef05c021e8d9
|
4
|
+
data.tar.gz: f7e65b01aa540e9023c30c9cefcbf2fd7381fb71b24852043c82e81e878bc8f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 857502906ac831709d69a371f4da5ca2bce260f55f72731f3db48be28b159d0816080e2dd90f87301653c609dc5d0dd27ec36d0e58f0649c45d18e9046851bc0
|
7
|
+
data.tar.gz: f54eb2b5e13a75b691fcb3efcc3b63d921a33194caf2f4d62147f8d7090faa4cfa70abfc379315bed61378176f2ef8919747990ed9ba8ff4f3886a32f54edbd3
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
@@ -4,15 +4,23 @@
|
|
4
4
|
*= require_self
|
5
5
|
*/
|
6
6
|
|
7
|
+
html {
|
8
|
+
box-sizing: border-box;
|
9
|
+
font-size: 14px;
|
10
|
+
}
|
11
|
+
|
12
|
+
*, *:before, *:after {
|
13
|
+
box-sizing: inherit;
|
14
|
+
}
|
15
|
+
|
7
16
|
body {
|
8
17
|
margin: 0;
|
9
18
|
padding: 20px;
|
10
19
|
background-color: #eee;
|
11
20
|
}
|
12
21
|
|
13
|
-
body
|
14
|
-
font-family: "
|
15
|
-
font-size: 14px;
|
22
|
+
body {
|
23
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
16
24
|
line-height: 1.4;
|
17
25
|
color: #333;
|
18
26
|
}
|
@@ -30,7 +38,6 @@ table {
|
|
30
38
|
width: 100%;
|
31
39
|
border-collapse: collapse;
|
32
40
|
border-spacing: 0;
|
33
|
-
margin-bottom: 20px;
|
34
41
|
table-layout: fixed;
|
35
42
|
}
|
36
43
|
|
@@ -40,13 +47,11 @@ th {
|
|
40
47
|
}
|
41
48
|
|
42
49
|
h1 {
|
43
|
-
margin-top: 0;
|
44
50
|
font-size: 20px;
|
45
51
|
font-weight: bold;
|
46
52
|
}
|
47
53
|
|
48
54
|
h2 {
|
49
|
-
margin-top: 0;
|
50
55
|
font-size: 16px;
|
51
56
|
font-weight: bold;
|
52
57
|
}
|
@@ -57,8 +62,13 @@ h1 small {
|
|
57
62
|
color: #999;
|
58
63
|
}
|
59
64
|
|
60
|
-
h1, p {
|
61
|
-
margin-
|
65
|
+
h1, h2, p, ul, hr, table, pre {
|
66
|
+
margin-top: 0;
|
67
|
+
margin-bottom: 1rem;
|
68
|
+
}
|
69
|
+
|
70
|
+
.field {
|
71
|
+
margin-bottom: 0.5rem;
|
62
72
|
}
|
63
73
|
|
64
74
|
h3 {
|
@@ -66,16 +76,16 @@ h3 {
|
|
66
76
|
}
|
67
77
|
|
68
78
|
ul {
|
79
|
+
list-style-type: disc;
|
80
|
+
padding-left: 20px;
|
81
|
+
}
|
82
|
+
|
83
|
+
ul.nav {
|
69
84
|
list-style-type: none;
|
70
85
|
padding: 0;
|
71
86
|
margin: 0;
|
72
87
|
}
|
73
88
|
|
74
|
-
ul.list-normal {
|
75
|
-
list-style-type: disc;
|
76
|
-
padding-left: 20px;
|
77
|
-
}
|
78
|
-
|
79
89
|
table td, table th {
|
80
90
|
padding: 8px;
|
81
91
|
}
|
@@ -98,13 +108,13 @@ textarea {
|
|
98
108
|
height: 120px;
|
99
109
|
border: solid 1px #ddd;
|
100
110
|
padding: 10px;
|
111
|
+
font-size: inherit;
|
101
112
|
}
|
102
113
|
|
103
114
|
hr {
|
104
115
|
border: none;
|
105
116
|
height: 0;
|
106
117
|
border-top: solid 1px #ddd;
|
107
|
-
margin-bottom: 15px;
|
108
118
|
}
|
109
119
|
|
110
120
|
.container {
|
@@ -171,9 +171,9 @@ module PgHero
|
|
171
171
|
if @show_details
|
172
172
|
query_hash_stats = @database.query_hash_stats(@query_hash, user: @user)
|
173
173
|
|
174
|
-
@chart_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at], (r[:total_minutes] * 60 * 1000).round] }, library: chart_library_options}]
|
175
|
-
@chart2_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at], r[:average_time].round(1)] }, library: chart_library_options}]
|
176
|
-
@chart3_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at], r[:calls]] }, library: chart_library_options}]
|
174
|
+
@chart_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at].change(sec: 0), (r[:total_minutes] * 60 * 1000).round] }, library: chart_library_options}]
|
175
|
+
@chart2_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at].change(sec: 0), r[:average_time].round(1)] }, library: chart_library_options}]
|
176
|
+
@chart3_data = [{name: "Value", data: query_hash_stats.map { |r| [r[:captured_at].change(sec: 0), r[:calls]] }, library: chart_library_options}]
|
177
177
|
|
178
178
|
@origins = Hash[query_hash_stats.group_by { |r| r[:origin].to_s }.map { |k, v| [k, v.size] }]
|
179
179
|
@total_count = query_hash_stats.size
|
@@ -286,7 +286,7 @@ module PgHero
|
|
286
286
|
|
287
287
|
def kill
|
288
288
|
if @database.kill(params[:pid])
|
289
|
-
|
289
|
+
redirect_backward notice: "Query killed"
|
290
290
|
else
|
291
291
|
redirect_backward notice: "Query no longer running"
|
292
292
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module PgHero
|
2
|
-
module
|
2
|
+
module HomeHelper
|
3
3
|
def pghero_pretty_ident(table, schema: nil)
|
4
4
|
ident = table
|
5
5
|
if schema && schema != "public"
|
@@ -11,5 +11,9 @@ module PgHero
|
|
11
11
|
@database.quote_ident(ident)
|
12
12
|
end
|
13
13
|
end
|
14
|
+
|
15
|
+
def pghero_js_var(name, value)
|
16
|
+
"var #{name} = #{json_escape(value.to_json(root: false))};".html_safe
|
17
|
+
end
|
14
18
|
end
|
15
19
|
end
|
@@ -5,12 +5,12 @@
|
|
5
5
|
</div>
|
6
6
|
|
7
7
|
<script>
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
<%= pghero_js_var("sort", @sort) %>
|
9
|
+
<%= pghero_js_var("minAverageTime", @min_average_time) %>
|
10
|
+
<%= pghero_js_var("minCalls", @min_calls) %>
|
11
|
+
<%= pghero_js_var("debug", @debug) %>
|
12
|
+
<%= pghero_js_var("startAt", params[:start_at] ? @start_at.to_i * 1000 : nil) %>
|
13
|
+
<%= pghero_js_var("endAt", @end_at.to_i * 1000) %>
|
14
14
|
|
15
15
|
initSlider();
|
16
16
|
</script>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<h1>Explain</h1>
|
3
3
|
|
4
4
|
<%= form_tag explain_path do %>
|
5
|
-
|
5
|
+
<div class="field"><%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %></div>
|
6
6
|
<p>
|
7
7
|
<%= submit_tag "Explain", class: "btn btn-info", style: "margin-right: 10px;" %>
|
8
8
|
<%= submit_tag "Analyze", class: "btn btn-danger", style: "margin-right: 10px;" %>
|
@@ -16,7 +16,7 @@
|
|
16
16
|
<% end %>
|
17
17
|
<pre><code><%= @explanation %></code></pre>
|
18
18
|
<% unless @visualize %>
|
19
|
-
<p><%= link_to "See how to interpret this", "
|
19
|
+
<p><%= link_to "See how to interpret this", "https://www.postgresql.org/docs/current/static/using-explain.html", target: "_blank" %></p>
|
20
20
|
<% end %>
|
21
21
|
<% if (index = @suggested_index) %>
|
22
22
|
<%= render partial: "suggested_index", locals: {index: index, details: index[:details]} %>
|
@@ -89,7 +89,7 @@
|
|
89
89
|
<td><%= table %></td>
|
90
90
|
<td><%= @row_counts[table] %></td>
|
91
91
|
<td>
|
92
|
-
<ul
|
92
|
+
<ul>
|
93
93
|
<% @indexes_by_table[table].to_a.sort_by { |i| [i[:primary] ? 0 : 1, i[:columns]] }.each do |i3| %>
|
94
94
|
<li>
|
95
95
|
<%= i3[:columns].join(", ") %><% if i3[:using] != "btree" %>
|
@@ -3,24 +3,24 @@ databases:
|
|
3
3
|
# Database URL (defaults to app database)
|
4
4
|
# url: <%%= ENV["DATABASE_URL"] %>
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
# Add more databases
|
7
|
+
# other:
|
8
|
+
# url: <%%= ENV["OTHER_DATABASE_URL"] %>
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
# Minimum time for long running queries
|
11
|
+
# long_running_query_sec: 60
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
# Minimum average time for slow queries
|
14
|
+
# slow_query_ms: 20
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
# Minimum calls for slow queries
|
17
|
+
# slow_query_calls: 100
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
# Minimum connections for high connections warning
|
20
|
+
# total_connections_threshold: 500
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
# url: <%%= ENV["OTHER_DATABASE_URL"] %>
|
22
|
+
# Statement timeout for explain
|
23
|
+
# explain_timeout_sec: 10
|
24
24
|
|
25
25
|
# Time zone (defaults to app time zone)
|
26
26
|
# time_zone: "Pacific Time (US & Canada)"
|
data/lib/pghero.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
require "
|
1
|
+
# dependencies
|
2
|
+
require "active_support"
|
3
|
+
require "forwardable"
|
3
4
|
|
4
5
|
# methods
|
5
6
|
require "pghero/methods/basic"
|
@@ -21,18 +22,18 @@ require "pghero/methods/users"
|
|
21
22
|
|
22
23
|
require "pghero/database"
|
23
24
|
require "pghero/engine" if defined?(Rails)
|
24
|
-
|
25
|
-
# models
|
26
|
-
require "pghero/connection"
|
27
|
-
require "pghero/query_stats"
|
25
|
+
require "pghero/version"
|
28
26
|
|
29
27
|
module PgHero
|
28
|
+
autoload :Connection, "pghero/connection"
|
29
|
+
autoload :QueryStats, "pghero/query_stats"
|
30
|
+
|
30
31
|
class Error < StandardError; end
|
31
32
|
class NotEnabled < Error; end
|
32
33
|
|
33
34
|
# settings
|
34
35
|
class << self
|
35
|
-
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations
|
36
|
+
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path
|
36
37
|
end
|
37
38
|
self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
|
38
39
|
self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
|
@@ -42,6 +43,7 @@ module PgHero
|
|
42
43
|
self.cache_hit_rate_threshold = 99
|
43
44
|
self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
44
45
|
self.show_migrations = true
|
46
|
+
self.config_path = ENV["PGHERO_CONFIG_PATH"] || "config/pghero.yml"
|
45
47
|
|
46
48
|
class << self
|
47
49
|
extend Forwardable
|
@@ -68,7 +70,10 @@ module PgHero
|
|
68
70
|
|
69
71
|
def config
|
70
72
|
@config ||= begin
|
71
|
-
|
73
|
+
require "erb"
|
74
|
+
require "yaml"
|
75
|
+
|
76
|
+
path = config_path
|
72
77
|
|
73
78
|
config_file_exists = File.exist?(path)
|
74
79
|
|
data/lib/pghero/database.rb
CHANGED
@@ -75,9 +75,9 @@ module PgHero
|
|
75
75
|
end
|
76
76
|
case url
|
77
77
|
when String
|
78
|
-
url = "#{url}#{url.include?("?") ? "&" : "?"}connect_timeout=
|
78
|
+
url = "#{url}#{url.include?("?") ? "&" : "?"}connect_timeout=5" unless url.include?("connect_timeout=")
|
79
79
|
when Hash
|
80
|
-
url[:connect_timeout] ||=
|
80
|
+
url[:connect_timeout] ||= 5
|
81
81
|
end
|
82
82
|
establish_connection url if url
|
83
83
|
end
|
data/lib/pghero/methods/basic.rb
CHANGED
@@ -27,7 +27,7 @@ module PgHero
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def quote_ident(value)
|
30
|
-
|
30
|
+
connection.quote_column_name(value)
|
31
31
|
end
|
32
32
|
|
33
33
|
private
|
@@ -37,7 +37,7 @@ module PgHero
|
|
37
37
|
# squish for logs
|
38
38
|
retries = 0
|
39
39
|
begin
|
40
|
-
result = conn.select_all(squish(sql))
|
40
|
+
result = conn.select_all(add_source(squish(sql)))
|
41
41
|
cast_method = ActiveRecord::VERSION::MAJOR < 5 ? :type_cast : :cast_value
|
42
42
|
result.map { |row| Hash[row.map { |col, val| [col.to_sym, result.column_types[col].send(cast_method, val)] }] }
|
43
43
|
rescue ActiveRecord::StatementInvalid => e
|
@@ -73,7 +73,7 @@ module PgHero
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def execute(sql)
|
76
|
-
connection.execute(sql)
|
76
|
+
connection.execute(add_source(sql))
|
77
77
|
end
|
78
78
|
|
79
79
|
def connection
|
@@ -95,6 +95,10 @@ module PgHero
|
|
95
95
|
str.to_s.gsub(/\A[[:space:]]+/, "").gsub(/[[:space:]]+\z/, "").gsub(/[[:space:]]+/, " ")
|
96
96
|
end
|
97
97
|
|
98
|
+
def add_source(sql)
|
99
|
+
"#{sql} /*pghero*/"
|
100
|
+
end
|
101
|
+
|
98
102
|
def quote(value)
|
99
103
|
connection.quote(value)
|
100
104
|
end
|
data/lib/pghero/methods/kill.rb
CHANGED
@@ -5,8 +5,8 @@ module PgHero
|
|
5
5
|
select_one("SELECT pg_terminate_backend(#{pid.to_i})")
|
6
6
|
end
|
7
7
|
|
8
|
-
def kill_long_running_queries
|
9
|
-
|
8
|
+
def kill_long_running_queries(min_duration: nil)
|
9
|
+
running_queries(min_duration: min_duration || long_running_query_sec).each { |query| kill(query[:pid]) }
|
10
10
|
true
|
11
11
|
end
|
12
12
|
|
@@ -10,7 +10,7 @@ module PgHero
|
|
10
10
|
|
11
11
|
# https://www.postgresql.org/message-id/CADKbJJWz9M0swPT3oqe8f9+tfD4-F54uE6Xtkh4nERpVsQnjnw@mail.gmail.com
|
12
12
|
def replication_lag
|
13
|
-
with_feature_support do
|
13
|
+
with_feature_support(:replication_lag) do
|
14
14
|
lag_condition =
|
15
15
|
if server_version_num >= 100000
|
16
16
|
"pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn()"
|
@@ -31,7 +31,7 @@ module PgHero
|
|
31
31
|
|
32
32
|
def replication_slots
|
33
33
|
if server_version_num >= 90400
|
34
|
-
with_feature_support([]) do
|
34
|
+
with_feature_support(:replication_slots, []) do
|
35
35
|
select_all <<-SQL
|
36
36
|
SELECT
|
37
37
|
slot_name,
|
@@ -46,18 +46,26 @@ module PgHero
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def replicating?
|
49
|
-
with_feature_support(false) do
|
49
|
+
with_feature_support(:replicating?, false) do
|
50
50
|
select_all("SELECT state FROM pg_stat_replication").any?
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
54
|
private
|
55
55
|
|
56
|
-
def
|
56
|
+
def feature_support
|
57
|
+
@feature_support ||= {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def with_feature_support(cache_key, default = nil)
|
61
|
+
# cache feature support to minimize errors in logs
|
62
|
+
return default if feature_support[cache_key] == false
|
63
|
+
|
57
64
|
begin
|
58
65
|
yield
|
59
66
|
rescue ActiveRecord::StatementInvalid => e
|
60
67
|
raise unless e.message.start_with?("PG::FeatureNotSupported:")
|
68
|
+
feature_support[cache_key] = false
|
61
69
|
default
|
62
70
|
end
|
63
71
|
end
|
@@ -5,6 +5,8 @@ module PgHero
|
|
5
5
|
# get columns with default values
|
6
6
|
# use pg_get_expr to get correct default value
|
7
7
|
# it's what information_schema.columns uses
|
8
|
+
# also, exclude temporary tables to prevent error
|
9
|
+
# when accessing across sessions
|
8
10
|
sequences = select_all <<-SQL
|
9
11
|
SELECT
|
10
12
|
n.nspname AS table_schema,
|
@@ -24,6 +26,7 @@ module PgHero
|
|
24
26
|
NOT a.attisdropped
|
25
27
|
AND a.attnum > 0
|
26
28
|
AND d.adsrc LIKE 'nextval%'
|
29
|
+
AND n.nspname NOT LIKE 'pg\\_temp\\_%'
|
27
30
|
SQL
|
28
31
|
|
29
32
|
# parse out sequence
|
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: 2.2.
|
4
|
+
version: 2.2.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:
|
11
|
+
date: 2019-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -129,7 +129,7 @@ files:
|
|
129
129
|
- app/assets/stylesheets/pghero/arduino-light.css
|
130
130
|
- app/assets/stylesheets/pghero/jquery.nouislider.css
|
131
131
|
- app/controllers/pg_hero/home_controller.rb
|
132
|
-
- app/helpers/pg_hero/
|
132
|
+
- app/helpers/pg_hero/home_helper.rb
|
133
133
|
- app/views/layouts/pg_hero/application.html.erb
|
134
134
|
- app/views/pg_hero/home/_connections_table.html.erb
|
135
135
|
- app/views/pg_hero/home/_live_queries_table.html.erb
|
@@ -197,8 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
197
|
- !ruby/object:Gem::Version
|
198
198
|
version: '0'
|
199
199
|
requirements: []
|
200
|
-
|
201
|
-
rubygems_version: 2.7.7
|
200
|
+
rubygems_version: 3.0.3
|
202
201
|
signing_key:
|
203
202
|
specification_version: 4
|
204
203
|
summary: A performance dashboard for Postgres
|